From 3e633dfd371f7606898e189210c132988fea2f03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Sat, 18 Sep 2021 00:57:17 -0700 Subject: [PATCH 01/14] let the great rewrite begin --- miette-derive/src/diagnostic.rs | 4 +- miette-derive/src/forward.rs | 9 ++-- miette-derive/src/snippets.rs | 2 +- src/eyreish/context.rs | 12 ++--- src/eyreish/wrapper.rs | 24 +++++++++- src/handlers/debug.rs | 6 +-- src/handlers/graphical.rs | 16 ++++--- src/handlers/narratable.rs | 80 +++++---------------------------- src/named_source.rs | 10 ++--- src/protocol.rs | 45 +++++++------------ src/source_impls.rs | 18 ++++---- 11 files changed, 86 insertions(+), 140 deletions(-) diff --git a/miette-derive/src/diagnostic.rs b/miette-derive/src/diagnostic.rs index db75868c..8bf747e7 100644 --- a/miette-derive/src/diagnostic.rs +++ b/miette-derive/src/diagnostic.rs @@ -209,7 +209,7 @@ impl Diagnostic { let help_method = forward.gen_struct_method(WhichFn::Help); let url_method = forward.gen_struct_method(WhichFn::Url); let severity_method = forward.gen_struct_method(WhichFn::Severity); - let snippets_method = forward.gen_struct_method(WhichFn::Snippets); + let snippets_method = forward.gen_struct_method(WhichFn::Labels); quote! { impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause { @@ -247,7 +247,7 @@ impl Diagnostic { .snippets .as_ref() .and_then(|x| x.gen_struct(fields)) - .or_else(|| forward(WhichFn::Snippets)); + .or_else(|| forward(WhichFn::Labels)); let url_body = concrete .url .as_ref() diff --git a/miette-derive/src/forward.rs b/miette-derive/src/forward.rs index 36177d62..2adbf90e 100644 --- a/miette-derive/src/forward.rs +++ b/miette-derive/src/forward.rs @@ -35,7 +35,7 @@ pub enum WhichFn { Help, Url, Severity, - Snippets, + Labels, } impl WhichFn { @@ -45,7 +45,7 @@ impl WhichFn { Self::Help => quote! { help() }, Self::Url => quote! { url() }, Self::Severity => quote! { severity() }, - Self::Snippets => quote! { snippets() }, + Self::Labels => quote! { snippets() }, } } @@ -63,12 +63,9 @@ impl WhichFn { Self::Severity => quote! { fn severity(&self) -> std::option::Option }, - Self::Snippets => quote! { + Self::Labels => quote! { fn snippets(&self) -> std::option::Option + '_>> }, - Self::Related => quote! { - fn related<'a>(&'a self) -> std::option::Option + 'a>> - }, } } diff --git a/miette-derive/src/snippets.rs b/miette-derive/src/snippets.rs index 26a53b1f..b853c844 100644 --- a/miette-derive/src/snippets.rs +++ b/miette-derive/src/snippets.rs @@ -282,7 +282,7 @@ impl Snippets { pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option { gen_all_variants_with( variants, - WhichFn::Snippets, + WhichFn::Labels, |ident, fields, DiagnosticConcreteArgs { snippets, .. }| { let (display_pat, display_members) = display_pat_members(fields); snippets.as_ref().and_then(|snippets| { diff --git a/src/eyreish/context.rs b/src/eyreish/context.rs index 4d16fc39..2fd01652 100644 --- a/src/eyreish/context.rs +++ b/src/eyreish/context.rs @@ -141,10 +141,8 @@ where self.error.url() } - fn snippets<'a>( - &'a self, - ) -> Option> + 'a>> { - self.error.snippets() + fn labels<'a>(&'a self) -> Option + 'a>> { + self.error.labels() } } @@ -168,10 +166,8 @@ where self.error.inner.diagnostic().url() } - fn snippets<'a>( - &'a self, - ) -> Option> + 'a>> { - self.error.inner.diagnostic().snippets() + fn labels<'a>(&'a self) -> Option + 'a>> { + self.error.inner.diagnostic().labels() } } diff --git a/src/eyreish/wrapper.rs b/src/eyreish/wrapper.rs index 3104dade..22ae8149 100644 --- a/src/eyreish/wrapper.rs +++ b/src/eyreish/wrapper.rs @@ -71,11 +71,31 @@ impl Display for NoneError { impl StdError for NoneError {} impl Diagnostic for NoneError {} -#[derive(miette_derive::Diagnostic)] #[repr(transparent)] -#[diagnostic(transparent)] pub(crate) struct BoxedError(pub(crate) Box); +impl Diagnostic for BoxedError { + fn code<'a>(&'a self) -> Option> { + self.0.code() + } + + fn severity(&self) -> Option { + self.0.severity() + } + + fn help<'a>(&'a self) -> Option> { + self.0.help() + } + + fn url<'a>(&'a self) -> Option> { + self.0.url() + } + + fn labels<'a>(&'a self) -> Option + 'a>> { + self.0.labels() + } +} + impl Debug for BoxedError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Debug::fmt(&self.0, f) diff --git a/src/handlers/debug.rs b/src/handlers/debug.rs index 22488329..e6f07330 100644 --- a/src/handlers/debug.rs +++ b/src/handlers/debug.rs @@ -48,9 +48,9 @@ impl DebugReportHandler { if let Some(help) = diagnostic.help() { diag.field("help", &help.to_string()); } - if let Some(snippets) = diagnostic.snippets() { - let snippets: Vec<_> = snippets.collect(); - diag.field("snippets", &format!("{:?}", snippets)); + if let Some(labels) = diagnostic.labels() { + let labels: Vec<_> = labels.collect(); + diag.field("labels", &format!("{:?}", labels)); } diag.finish()?; writeln!(f)?; diff --git a/src/handlers/graphical.rs b/src/handlers/graphical.rs index 7e52b9c6..5b1ad2ac 100644 --- a/src/handlers/graphical.rs +++ b/src/handlers/graphical.rs @@ -5,7 +5,7 @@ use unicode_width::UnicodeWidthStr; use crate::chain::Chain; use crate::handlers::theme::*; -use crate::protocol::{Diagnostic, DiagnosticSnippet, Severity}; +use crate::protocol::{Diagnostic, Severity}; use crate::{ReportHandler, SourceSpan, SpanContents}; /** @@ -94,12 +94,12 @@ impl GraphicalReportHandler { writeln!(f)?; self.render_causes(f, diagnostic)?; - if let Some(snippets) = diagnostic.snippets() { - for snippet in snippets { - writeln!(f)?; - self.render_snippet(f, &snippet)?; - } - } + // if let Some(snippets) = diagnostic.snippets() { + // for snippet in snippets { + // writeln!(f)?; + // self.render_snippet(f, &snippet)?; + // } + // } self.render_footer(f, diagnostic)?; Ok(()) @@ -228,6 +228,7 @@ impl GraphicalReportHandler { Ok(()) } + /* fn render_snippet( &self, f: &mut impl fmt::Write, @@ -628,6 +629,7 @@ impl GraphicalReportHandler { } Ok((context_data, lines)) } + */ } impl ReportHandler for GraphicalReportHandler { diff --git a/src/handlers/narratable.rs b/src/handlers/narratable.rs index 84823300..31ec81c7 100644 --- a/src/handlers/narratable.rs +++ b/src/handlers/narratable.rs @@ -1,8 +1,8 @@ use std::fmt; use crate::chain::Chain; -use crate::protocol::{Diagnostic, DiagnosticSnippet, Severity}; -use crate::{ReportHandler, SourceSpan, SpanContents}; +use crate::protocol::{Diagnostic, Severity}; +use crate::{ReportHandler, SourceCode, SourceSpan, SpanContents}; /** [ReportHandler] that renders plain text and avoids extraneous graphics. @@ -18,7 +18,7 @@ impl NarratableReportHandler { /// Create a new [NarratableReportHandler]. There are no customization /// options. pub fn new() -> Self { - Self { footer: None} + Self { footer: None } } /// Set the footer to be displayed at the end of the report. @@ -47,12 +47,11 @@ impl NarratableReportHandler { self.render_header(f, diagnostic)?; self.render_causes(f, diagnostic)?; - if let Some(snippets) = diagnostic.snippets() { - for snippet in snippets { - writeln!(f)?; - self.render_snippet(f, &snippet)?; - } - } + // if let Some(labels) = diagnostic.labels() { + // for label in labels { + // self.render_label(f, &label)?; + // } + // } self.render_footer(f, diagnostic)?; Ok(()) @@ -92,68 +91,12 @@ impl NarratableReportHandler { Ok(()) } - fn render_snippet( - &self, - f: &mut impl fmt::Write, - snippet: &DiagnosticSnippet<'_>, - ) -> fmt::Result { - let (contents, lines) = self.get_lines(snippet)?; - - write!(f, "Begin snippet")?; - if let Some(filename) = snippet.source.name() { - write!(f, " for {}", filename,)?; - } - write!( - f, - " starting at line {}, column {}", - contents.line() + 1, - contents.column() + 1 - )?; - if let Some(message) = snippet.message.as_deref() { - write!(f, ": {}", message)?; - } - writeln!(f)?; - writeln!(f)?; - - // 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 - // gutter size. - let mut highlights = snippet.highlights.clone().unwrap_or_else(Vec::new); - // sorting is your friend. - highlights.sort_unstable_by_key(|(_, h)| h.offset()); - - // Now it's time for the fun part--actually rendering everything! - for line in &lines { - writeln!(f, "snippet line {}: {}", line.line_number, line.text)?; - let relevant = highlights.iter().filter(|(_, hl)| line.span_starts(hl)); - for (label, hl) in relevant { - let contents = snippet.source.read_span(hl).map_err(|_| fmt::Error)?; - if contents.line() + 1 == line.line_number { - write!( - f, - " highlight starting at line {}, column {}", - contents.line() + 1, - contents.column() + 1 - )?; - if let Some(label) = label { - write!(f, ": {}", label)?; - } - writeln!(f)?; - } - } - } - writeln!(f)?; - Ok(()) - } - + /* fn get_lines<'a>( &'a self, - snippet: &'a DiagnosticSnippet<'a>, + source: &'a dyn Source, ) -> Result<(Box, Vec), fmt::Error> { - let context_data = snippet - .source - .read_span(&snippet.context) - .map_err(|_| fmt::Error)?; + let context_data = source.read_span(&snippet.context).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(); @@ -200,6 +143,7 @@ impl NarratableReportHandler { } Ok((context_data, lines)) } + */ } impl ReportHandler for NarratableReportHandler { diff --git a/src/named_source.rs b/src/named_source.rs index 0a6270d9..6520780c 100644 --- a/src/named_source.rs +++ b/src/named_source.rs @@ -1,17 +1,17 @@ -use crate::Source; +use crate::SourceCode; /// Utility struct for when you have a regular [Source] type, such as a String, /// that doesn't implement `name`, or if you want to override the `.name()` /// returned by the `Source`. #[derive(Debug)] pub struct NamedSource { - source: Box, + source: Box, name: String, } impl NamedSource { /// Create a new [NamedSource] using a regular [Source] and giving it a [Source::name]. - pub fn new(name: impl AsRef, source: impl Source + Send + Sync + 'static) -> Self { + pub fn new(name: impl AsRef, source: impl SourceCode + Send + Sync + 'static) -> Self { Self { source: Box::new(source), name: name.as_ref().to_string(), @@ -19,12 +19,12 @@ impl NamedSource { } /// Returns a reference the inner [Source] type for this [NamedSource]. - pub fn inner(&self) -> &(dyn Source + Send + Sync + 'static) { + pub fn inner(&self) -> &(dyn SourceCode + Send + Sync + 'static) { &*self.source } } -impl Source for NamedSource { +impl SourceCode for NamedSource { fn read_span<'a>( &'a self, span: &crate::SourceSpan, diff --git a/src/protocol.rs b/src/protocol.rs index f642defe..7eac508b 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -45,9 +45,13 @@ pub trait Diagnostic: std::error::Error { None } - /// Additional contextual snippets. This is typically used for adding - /// marked-up source file output the way compilers often do. - fn snippets<'a>(&'a self) -> Option> + 'a>> { + /// Source code to apply this Diagnostic's [Diagnostic::labels] to. + fn source_code(&self) -> Option<&dyn SourceCode> { + None + } + + /// Labels to apply to this Diagnostic's [Diagnostic::source_code] + fn labels<'a>(&'a self) -> Option + 'a>> { None } } @@ -168,7 +172,7 @@ If you can read it, you can source it, and it's not necessary to read the whole thing--meaning you should be able to support Sources which are gigabytes or larger in size. */ -pub trait Source: std::fmt::Debug + Send + Sync { +pub trait SourceCode: std::fmt::Debug + Send + Sync { /// Read the bytes for a specific span from this Source. fn read_span<'a>( &'a self, @@ -182,16 +186,16 @@ pub trait Source: std::fmt::Debug + Send + Sync { } /** -Contents of a [Source] covered by [SourceSpan]. +Contents of a [SourceCode] covered by [SourceSpan]. Includes line and column information to optimize highlight calculations. */ pub trait SpanContents { /// Reference to the data inside the associated span, in bytes. fn data(&self) -> &[u8]; - /// The 0-indexed line in the associated [Source] where the data begins. + /// The 0-indexed line in the associated [SourceCode] where the data begins. fn line(&self) -> usize; - /// The 0-indexed column in the associated [Source] where the data begins, + /// The 0-indexed column in the associated [SourceCode] where the data begins, /// relative to `line`. fn column(&self) -> usize; } @@ -201,7 +205,7 @@ Basic implementation of the [SpanContents] trait, for convenience. */ #[derive(Clone, Debug)] pub struct MietteSpanContents<'a> { - /// Data from a [Source], in bytes. + /// Data from a [SourceCode], in bytes. data: &'a [u8], // The 0-indexed line where the associated [SourceSpan] _starts_. line: usize, @@ -229,24 +233,7 @@ impl<'a> SpanContents for MietteSpanContents<'a> { } /** -A snippet from a [Source] to be displayed with a message and possibly some highlights. - */ -#[derive(Clone, Debug)] -pub struct DiagnosticSnippet<'a> { - /// Explanation of this specific diagnostic snippet. - pub message: Option, - /// A [Source] that can be used to read the actual text of a source. - pub source: &'a (dyn Source), - /// The primary [SourceSpan] where this diagnostic is located. - pub context: SourceSpan, - /// Additional [SourceSpan]s that mark specific sections of the span, for - /// example, to underline specific text within the larger span. They're - /// paired with labels that should be applied to those sections. - pub highlights: Option, SourceSpan)>>, -} - -/** -Span within a [Source] with an associated message. +Span within a [SourceCode] with an associated message. */ #[derive(Clone, Debug)] pub struct SourceSpan { @@ -265,7 +252,7 @@ impl SourceSpan { } } - /// The absolute offset, in bytes, from the beginning of a [Source]. + /// The absolute offset, in bytes, from the beginning of a [SourceCode]. pub fn offset(&self) -> usize { self.offset.offset() } @@ -301,12 +288,12 @@ impl From<(SourceOffset, SourceOffset)> for SourceSpan { } /** -"Raw" type for the byte offset from the beginning of a [Source]. +"Raw" type for the byte offset from the beginning of a [SourceCode]. */ pub type ByteOffset = usize; /** -Newtype that represents the [ByteOffset] from the beginning of a [Source] +Newtype that represents the [ByteOffset] from the beginning of a [SourceCode] */ #[derive(Clone, Copy, Debug)] pub struct SourceOffset(ByteOffset); diff --git a/src/source_impls.rs b/src/source_impls.rs index 8e5c8f70..d9517d4b 100644 --- a/src/source_impls.rs +++ b/src/source_impls.rs @@ -1,5 +1,5 @@ /*! -Default trait implementations for [Source]. +Default trait implementations for [SourceCode]. */ use std::{ borrow::{Cow, ToOwned}, @@ -7,7 +7,7 @@ use std::{ sync::Arc, }; -use crate::{MietteError, MietteSpanContents, Source, SourceSpan, SpanContents}; +use crate::{MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents}; fn start_line_column(string: &str, span: &SourceSpan) -> Result<(usize, usize), MietteError> { let mut offset = 0usize; @@ -46,7 +46,7 @@ fn start_line_column(string: &str, span: &SourceSpan) -> Result<(usize, usize), // The basic impl here is on str (not &str), because otherwise String's impl cannot reuse it // without creating a temporary &str inside its read_span implementation, and then returning data // that refers to that temporary. -impl Source for str { +impl SourceCode for str { fn read_span<'a>( &'a self, span: &SourceSpan, @@ -61,25 +61,25 @@ impl Source for str { } /// Makes `src: &'static str` or `struct S<'a> { src: &'a str }` usable. -impl<'s> Source for &'s str { +impl<'s> SourceCode for &'s str { fn read_span<'a>( &'a self, span: &SourceSpan, ) -> Result, MietteError> { - ::read_span(self, span) + ::read_span(self, span) } } -impl Source for String { +impl SourceCode for String { fn read_span<'a>( &'a self, span: &SourceSpan, ) -> Result, MietteError> { - ::read_span(self, span) + ::read_span(self, span) } } -impl Source for Arc { +impl SourceCode for Arc { fn read_span<'a>( &'a self, span: &SourceSpan, @@ -88,7 +88,7 @@ impl Source for Arc { } } -impl Source for Cow<'_, T> +impl SourceCode for Cow<'_, T> where // The minimal bounds are used here. `T::Owned` need not be `Source`, because `&T` can always // be obtained from `Cow<'_, T>`. From e2203284edea6b3064b377e12fe1c7c0d49aaf80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Sat, 18 Sep 2021 09:23:00 -0700 Subject: [PATCH 02/14] switch start_line_column to work on bytes --- src/source_impls.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/source_impls.rs b/src/source_impls.rs index d9517d4b..bd3cf1b6 100644 --- a/src/source_impls.rs +++ b/src/source_impls.rs @@ -9,22 +9,22 @@ use std::{ use crate::{MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents}; -fn start_line_column(string: &str, span: &SourceSpan) -> Result<(usize, usize), MietteError> { +fn start_line_column(string: &[u8], span: &SourceSpan) -> Result<(usize, usize), MietteError> { let mut offset = 0usize; let mut start_line = 0usize; let mut start_column = 0usize; - let mut iter = string.chars().peekable(); + let mut iter = string.iter().copied().peekable(); while let Some(char) = iter.next() { if offset < span.offset() { match char { - '\r' => { - if iter.next_if_eq(&'\n').is_some() { + b'\r' => { + if iter.next_if_eq(&b'\n').is_some() { offset += 1; } start_line += 1; start_column = 0; } - '\n' => { + b'\n' => { start_line += 1; start_column = 0; } @@ -38,7 +38,7 @@ fn start_line_column(string: &str, span: &SourceSpan) -> Result<(usize, usize), return Ok((start_line, start_column)); } - offset += char.len_utf8(); + offset += 1; } Err(MietteError::OutOfBounds) } @@ -51,9 +51,10 @@ impl SourceCode for str { &'a self, span: &SourceSpan, ) -> Result, MietteError> { - let (start_line, start_column) = start_line_column(self, span)?; + let bytes = self.as_bytes(); + let (start_line, start_column) = start_line_column(bytes, span)?; return Ok(Box::new(MietteSpanContents::new( - &self.as_bytes()[span.offset()..span.offset() + span.len()], + &bytes[span.offset()..span.offset() + span.len()], start_line, start_column, ))); From 8e19f8bfb8977ab19ec8baa56ecc025803ca9a5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Sat, 18 Sep 2021 15:58:26 -0700 Subject: [PATCH 03/14] automatic context collection: success! --- src/named_source.rs | 5 +- src/protocol.rs | 5 +- src/source_impls.rs | 133 +++++++++++++++++++++++++++++++++----------- 3 files changed, 109 insertions(+), 34 deletions(-) diff --git a/src/named_source.rs b/src/named_source.rs index 6520780c..484724f5 100644 --- a/src/named_source.rs +++ b/src/named_source.rs @@ -28,8 +28,11 @@ impl SourceCode for NamedSource { fn read_span<'a>( &'a self, span: &crate::SourceSpan, + context_lines_before: usize, + context_lines_after: usize, ) -> Result, crate::MietteError> { - self.source.read_span(span) + self.source + .read_span(span, context_lines_before, context_lines_after) } fn name(&self) -> Option { diff --git a/src/protocol.rs b/src/protocol.rs index 7eac508b..fdfde352 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -173,10 +173,13 @@ and it's not necessary to read the whole thing--meaning you should be able to support Sources which are gigabytes or larger in size. */ pub trait SourceCode: std::fmt::Debug + Send + Sync { - /// Read the bytes for a specific span from this Source. + /// Read the bytes for a specific span from this SourceCode, keeping a + /// certain number of lines before and after the span as context. fn read_span<'a>( &'a self, span: &SourceSpan, + context_lines_before: usize, + context_lines_after: usize, ) -> Result, MietteError>; /// Optional name, usually a filename, for this source. diff --git a/src/source_impls.rs b/src/source_impls.rs index bd3cf1b6..801ff815 100644 --- a/src/source_impls.rs +++ b/src/source_impls.rs @@ -9,65 +9,115 @@ use std::{ use crate::{MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents}; -fn start_line_column(string: &[u8], span: &SourceSpan) -> Result<(usize, usize), MietteError> { +fn context_info<'a>( + input: &'a [u8], + span: &SourceSpan, + context_lines_before: usize, + context_lines_after: usize, +) -> Result<(&'a [u8], usize, usize), MietteError> { let mut offset = 0usize; let mut start_line = 0usize; - let mut start_column = 0usize; - let mut iter = string.iter().copied().peekable(); + // We don't mess with this for now -- context always assume it starts at + // the beginning of a line. + let start_column = 0usize; + let mut before_lines_starts = Vec::new(); + let mut current_line_start = 0usize; + let mut end_lines = 0usize; + let mut post_span = false; + let mut iter = input.iter().copied().peekable(); while let Some(char) = iter.next() { - if offset < span.offset() { - match char { - b'\r' => { - if iter.next_if_eq(&b'\n').is_some() { + if matches!(char, b'\r' | b'\n') { + if char == b'\r' && iter.next_if_eq(&b'\n').is_some() { + offset += 1; + } + if offset < span.offset() { + // We're before the start of the span. + start_line += 1; + before_lines_starts.push(current_line_start); + if before_lines_starts.len() > context_lines_before { + before_lines_starts.remove(0); + } + } else if offset >= span.offset() + span.len() - 1 { + // We're after the end of the span, but haven't necessarily + // started collecting end lines yet (we might still be + // collecting context lines). + if post_span { + end_lines += 1; + if end_lines > context_lines_after { offset += 1; + break; } - start_line += 1; - start_column = 0; - } - b'\n' => { - start_line += 1; - start_column = 0; - } - _ => { - start_column += 1; } } + current_line_start = offset + 1; } if offset >= span.offset() + span.len() - 1 { - return Ok((start_line, start_column)); + post_span = true; + if end_lines >= context_lines_after { + offset += 1; + break; + } } offset += 1; } - Err(MietteError::OutOfBounds) + if offset >= span.offset() + span.len() - 1 { + Ok(( + &input[before_lines_starts + .get(0) + .copied() + .unwrap_or_else(|| span.offset())..offset], + start_line, + start_column, + )) + } else { + Err(MietteError::OutOfBounds) + } } -// The basic impl here is on str (not &str), because otherwise String's impl cannot reuse it -// without creating a temporary &str inside its read_span implementation, and then returning data -// that refers to that temporary. -impl SourceCode for str { +impl SourceCode for [u8] { fn read_span<'a>( &'a self, span: &SourceSpan, + context_lines_before: usize, + context_lines_after: usize, ) -> Result, MietteError> { - let bytes = self.as_bytes(); - let (start_line, start_column) = start_line_column(bytes, span)?; + let (data, start_line, start_column) = + context_info(self, span, context_lines_before, context_lines_after)?; return Ok(Box::new(MietteSpanContents::new( - &bytes[span.offset()..span.offset() + span.len()], + data, start_line, start_column, ))); } } +impl SourceCode for str { + fn read_span<'a>( + &'a self, + span: &SourceSpan, + context_lines_before: usize, + context_lines_after: usize, + ) -> Result, MietteError> { + <[u8] as SourceCode>::read_span( + self.as_bytes(), + span, + context_lines_before, + context_lines_after, + ) + } +} + /// Makes `src: &'static str` or `struct S<'a> { src: &'a str }` usable. impl<'s> SourceCode for &'s str { fn read_span<'a>( &'a self, span: &SourceSpan, + context_lines_before: usize, + context_lines_after: usize, ) -> Result, MietteError> { - ::read_span(self, span) + ::read_span(self, span, context_lines_before, context_lines_after) } } @@ -75,8 +125,10 @@ impl SourceCode for String { fn read_span<'a>( &'a self, span: &SourceSpan, + context_lines_before: usize, + context_lines_after: usize, ) -> Result, MietteError> { - ::read_span(self, span) + ::read_span(self, span, context_lines_before, context_lines_after) } } @@ -84,8 +136,11 @@ impl SourceCode for Arc { fn read_span<'a>( &'a self, span: &SourceSpan, + context_lines_before: usize, + context_lines_after: usize, ) -> Result, MietteError> { - self.as_ref().read_span(span) + self.as_ref() + .read_span(span, context_lines_before, context_lines_after) } } @@ -98,8 +153,11 @@ where fn read_span<'a>( &'a self, span: &SourceSpan, + context_lines_before: usize, + context_lines_after: usize, ) -> Result, MietteError> { - self.as_ref().read_span(span) + self.as_ref() + .read_span(span, context_lines_before, context_lines_after) } } @@ -110,7 +168,7 @@ mod tests { #[test] fn basic() -> Result<(), MietteError> { let src = String::from("foo\n"); - let contents = src.read_span(&(0, 4).into())?; + let contents = src.read_span(&(0, 4).into(), 0, 0)?; assert_eq!("foo\n", std::str::from_utf8(contents.data()).unwrap()); Ok(()) } @@ -118,7 +176,7 @@ mod tests { #[test] fn middle() -> Result<(), MietteError> { let src = String::from("foo\nbar\nbaz\n"); - let contents = src.read_span(&(4, 4).into())?; + let contents = src.read_span(&(4, 4).into(), 0, 0)?; assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap()); Ok(()) } @@ -126,8 +184,19 @@ mod tests { #[test] fn with_crlf() -> Result<(), MietteError> { let src = String::from("foo\r\nbar\r\nbaz\r\n"); - let contents = src.read_span(&(5, 5).into())?; + let contents = src.read_span(&(5, 5).into(), 0, 0)?; assert_eq!("bar\r\n", std::str::from_utf8(contents.data()).unwrap()); Ok(()) } + + #[test] + fn with_context() -> Result<(), MietteError> { + let src = String::from("xxx\nfoo\nbar\nbaz\n\nyyy\n"); + let contents = src.read_span(&(10, 4).into(), 1, 2)?; + assert_eq!( + "foo\nbar\nbaz\n\n", + std::str::from_utf8(contents.data()).unwrap() + ); + Ok(()) + } } From 35e244903aecec971381470a19665ecbd0288136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Sat, 18 Sep 2021 16:32:48 -0700 Subject: [PATCH 04/14] fixed start column thing and improved tests --- src/source_impls.rs | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/src/source_impls.rs b/src/source_impls.rs index 801ff815..64909880 100644 --- a/src/source_impls.rs +++ b/src/source_impls.rs @@ -17,9 +17,7 @@ fn context_info<'a>( ) -> Result<(&'a [u8], usize, usize), MietteError> { let mut offset = 0usize; let mut start_line = 0usize; - // We don't mess with this for now -- context always assume it starts at - // the beginning of a line. - let start_column = 0usize; + let mut start_column = 0usize; let mut before_lines_starts = Vec::new(); let mut current_line_start = 0usize; let mut end_lines = 0usize; @@ -32,9 +30,10 @@ fn context_info<'a>( } if offset < span.offset() { // We're before the start of the span. - start_line += 1; + start_column = 0; before_lines_starts.push(current_line_start); if before_lines_starts.len() > context_lines_before { + start_line += 1; before_lines_starts.remove(0); } } else if offset >= span.offset() + span.len() - 1 { @@ -43,6 +42,7 @@ fn context_info<'a>( // collecting context lines). if post_span { end_lines += 1; + start_column = 0; if end_lines > context_lines_after { offset += 1; break; @@ -50,6 +50,8 @@ fn context_info<'a>( } } current_line_start = offset + 1; + } else if offset < span.offset() { + start_column += 1; } if offset >= span.offset() + span.len() - 1 { @@ -62,6 +64,7 @@ fn context_info<'a>( offset += 1; } + if offset >= span.offset() + span.len() - 1 { Ok(( &input[before_lines_starts @@ -69,7 +72,11 @@ fn context_info<'a>( .copied() .unwrap_or_else(|| span.offset())..offset], start_line, - start_column, + if context_lines_before == 0 { + start_column + } else { + 0 + }, )) } else { Err(MietteError::OutOfBounds) @@ -146,8 +153,8 @@ impl SourceCode for Arc { impl SourceCode for Cow<'_, T> where - // The minimal bounds are used here. `T::Owned` need not be `Source`, because `&T` can always - // be obtained from `Cow<'_, T>`. + // The minimal bounds are used here. `T::Owned` need not be `SourceCode`, + // because `&T` can always be obtained from `Cow<'_, T>`. T::Owned: Debug + Send + Sync, { fn read_span<'a>( @@ -170,6 +177,8 @@ mod tests { let src = String::from("foo\n"); let contents = src.read_span(&(0, 4).into(), 0, 0)?; assert_eq!("foo\n", std::str::from_utf8(contents.data()).unwrap()); + assert_eq!(0, contents.line()); + assert_eq!(0, contents.column()); Ok(()) } @@ -178,6 +187,18 @@ mod tests { let src = String::from("foo\nbar\nbaz\n"); let contents = src.read_span(&(4, 4).into(), 0, 0)?; assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap()); + assert_eq!(1, contents.line()); + assert_eq!(0, contents.column()); + Ok(()) + } + + #[test] + fn middle_of_line() -> Result<(), MietteError> { + let src = String::from("foo\nbarbar\nbaz\n"); + let contents = src.read_span(&(7, 4).into(), 0, 0)?; + assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap()); + assert_eq!(1, contents.line()); + assert_eq!(3, contents.column()); Ok(()) } @@ -186,17 +207,21 @@ mod tests { let src = String::from("foo\r\nbar\r\nbaz\r\n"); let contents = src.read_span(&(5, 5).into(), 0, 0)?; assert_eq!("bar\r\n", std::str::from_utf8(contents.data()).unwrap()); + assert_eq!(1, contents.line()); + assert_eq!(0, contents.column()); Ok(()) } #[test] fn with_context() -> Result<(), MietteError> { let src = String::from("xxx\nfoo\nbar\nbaz\n\nyyy\n"); - let contents = src.read_span(&(10, 4).into(), 1, 2)?; + let contents = src.read_span(&(8, 4).into(), 1, 2)?; assert_eq!( "foo\nbar\nbaz\n\n", std::str::from_utf8(contents.data()).unwrap() ); + assert_eq!(1, contents.line()); + assert_eq!(0, contents.column()); Ok(()) } } From 3969de0a8bf5152a07ed227eee876ac2b45925f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Sat, 18 Sep 2021 17:53:26 -0700 Subject: [PATCH 05/14] initial stab at updating the graphical handler --- Cargo.toml | 4 +- src/eyreish/context.rs | 6 +-- src/eyreish/wrapper.rs | 4 +- src/handlers/graphical.rs | 91 ++++++++++++++++++--------------------- src/protocol.rs | 42 ++++++++++++++++-- 5 files changed, 89 insertions(+), 58 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0533533d..3703f793 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ unicode-width = { version = "0.1.8", optional = true } supports-hyperlinks = { version = "1.1.0", optional = true } supports-color = { version = "1.0.2", optional = true } supports-unicode = { version = "1.0.0", optional = true } +itertools = { version = "0.10.1", optional = true } [dev-dependencies] semver = "1.0.4" @@ -47,7 +48,8 @@ fancy = [ "unicode-width", "supports-hyperlinks", "supports-color", - "supports-unicode" + "supports-unicode", + "itertools" ] [workspace] diff --git a/src/eyreish/context.rs b/src/eyreish/context.rs index 2fd01652..e2547a65 100644 --- a/src/eyreish/context.rs +++ b/src/eyreish/context.rs @@ -4,7 +4,7 @@ use core::fmt::{self, Debug, Display, Write}; use std::error::Error as StdError; -use crate::Diagnostic; +use crate::{Diagnostic, LabeledSpan}; mod ext { use super::*; @@ -141,7 +141,7 @@ where self.error.url() } - fn labels<'a>(&'a self) -> Option + 'a>> { + fn labels<'a>(&'a self) -> Option + 'a>> { self.error.labels() } } @@ -166,7 +166,7 @@ where self.error.inner.diagnostic().url() } - fn labels<'a>(&'a self) -> Option + 'a>> { + fn labels<'a>(&'a self) -> Option + 'a>> { self.error.inner.diagnostic().labels() } } diff --git a/src/eyreish/wrapper.rs b/src/eyreish/wrapper.rs index 22ae8149..e58ce4de 100644 --- a/src/eyreish/wrapper.rs +++ b/src/eyreish/wrapper.rs @@ -2,7 +2,7 @@ use core::fmt::{self, Debug, Display}; use std::error::Error as StdError; -use crate::Diagnostic; +use crate::{Diagnostic, LabeledSpan}; use crate as miette; @@ -91,7 +91,7 @@ impl Diagnostic for BoxedError { self.0.url() } - fn labels<'a>(&'a self) -> Option + 'a>> { + fn labels<'a>(&'a self) -> Option + 'a>> { self.0.labels() } } diff --git a/src/handlers/graphical.rs b/src/handlers/graphical.rs index 5b1ad2ac..2d5a089e 100644 --- a/src/handlers/graphical.rs +++ b/src/handlers/graphical.rs @@ -6,7 +6,7 @@ use unicode_width::UnicodeWidthStr; use crate::chain::Chain; use crate::handlers::theme::*; use crate::protocol::{Diagnostic, Severity}; -use crate::{ReportHandler, SourceSpan, SpanContents}; +use crate::{LabeledSpan, ReportHandler, SourceCode, SourceSpan, SpanContents}; /** A [ReportHandler] that displays a given [crate::Report] in a quasi-graphical @@ -94,6 +94,16 @@ impl GraphicalReportHandler { writeln!(f)?; self.render_causes(f, diagnostic)?; + if let Some(source) = diagnostic.source_code() { + if let Some(labels) = diagnostic.labels() { + let mut labels = labels.collect::>(); + labels.sort_unstable_by_key(|l| l.inner().offset()); + if !labels.is_empty() { + writeln!(f)?; + self.render_snippets(f, source, labels)?; + } + } + } // if let Some(snippets) = diagnostic.snippets() { // for snippet in snippets { // writeln!(f)?; @@ -228,24 +238,21 @@ impl GraphicalReportHandler { Ok(()) } - /* - fn render_snippet( + fn render_snippets( &self, f: &mut impl fmt::Write, - snippet: &DiagnosticSnippet<'_>, + source: &dyn SourceCode, + labels: Vec, ) -> fmt::Result { - 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 - // gutter size. - let mut highlights = snippet.highlights.clone().unwrap_or_else(Vec::new); - // sorting is your friend. - highlights.sort_unstable_by_key(|(_, h)| h.offset()); - let highlights = highlights - .into_iter() + let (contents, lines) = self.get_lines(source, &labels)?; + + // sorting is your friend + let labels = labels + .iter() .zip(self.theme.styles.highlights.iter().cloned().cycle()) - .map(|((label, hl), st)| FancySpan::new(label, hl, st)) + .map(|(label, st)| { + FancySpan::new(label.label().map(String::from), label.inner().clone(), st) + }) .collect::>(); // The max number of gutter-lines that will be active at any given @@ -254,7 +261,7 @@ impl GraphicalReportHandler { let mut max_gutter = 0usize; for line in &lines { let mut num_highlights = 0; - for hl in &highlights { + for hl in &labels { if !line.span_line_only(hl) && line.span_applies(hl) { num_highlights += 1; } @@ -271,34 +278,14 @@ impl GraphicalReportHandler { .len(); // Header - if let Some(msg) = &snippet.message { - writeln!( - f, - "{}{}{}", - " ".repeat(linum_width + 2), - self.theme.characters.ltop, - self.theme.characters.hbar.to_string().repeat(4) - )?; - writeln!( - f, - "{}{} error: {}", - " ".repeat(linum_width + 2), - self.theme.characters.vbar, - msg - )?; - } write!( f, "{}{}{}", " ".repeat(linum_width + 2), - if snippet.message.is_some() { - self.theme.characters.lcross - } else { - self.theme.characters.ltop - }, + self.theme.characters.ltop, self.theme.characters.hbar, )?; - if let Some(source_name) = snippet.source.name() { + if let Some(source_name) = source.name() { let source_name = source_name.style(self.theme.styles.link); writeln!( f, @@ -327,13 +314,13 @@ impl GraphicalReportHandler { // Then, we need to print the gutter, along with any fly-bys We // have separate gutters depending on whether we're on the actual // line, or on one of the "highlight lines" below it. - self.render_line_gutter(f, max_gutter, line, &highlights)?; + self.render_line_gutter(f, max_gutter, line, &labels)?; // And _now_ we can print out the line text itself! writeln!(f, "{}", line.text)?; // Next, we write all the highlights that apply to this particular line. - let (single_line, multi_line): (Vec<_>, Vec<_>) = highlights + let (single_line, multi_line): (Vec<_>, Vec<_>) = labels .iter() .filter(|hl| line.span_applies(hl)) .partition(|hl| line.span_line_only(hl)); @@ -341,14 +328,14 @@ impl GraphicalReportHandler { // no line number! self.write_no_linum(f, linum_width)?; // gutter _again_ - self.render_highlight_gutter(f, max_gutter, line, &highlights)?; + self.render_highlight_gutter(f, max_gutter, line, &labels)?; self.render_single_line_highlights( f, line, linum_width, max_gutter, &single_line, - &highlights, + &labels, )?; } for hl in multi_line { @@ -356,7 +343,7 @@ impl GraphicalReportHandler { // no line number! self.write_no_linum(f, linum_width)?; // gutter _again_ - self.render_highlight_gutter(f, max_gutter, line, &highlights)?; + self.render_highlight_gutter(f, max_gutter, line, &labels)?; self.render_multi_line_end(f, hl)?; } } @@ -572,16 +559,23 @@ impl GraphicalReportHandler { fn get_lines<'a>( &'a self, - snippet: &'a DiagnosticSnippet<'_>, + source: &'a dyn SourceCode, + labels: &'a [LabeledSpan], ) -> Result<(Box, Vec), fmt::Error> { - let context_data = snippet - .source - .read_span(&snippet.context) + 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) .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_span.offset(); let mut line_offset = offset; let mut iter = context.chars().peekable(); let mut line_str = String::new(); @@ -629,7 +623,6 @@ impl GraphicalReportHandler { } Ok((context_data, lines)) } - */ } impl ReportHandler for GraphicalReportHandler { diff --git a/src/protocol.rs b/src/protocol.rs index fdfde352..bbe32a42 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -51,7 +51,7 @@ pub trait Diagnostic: std::error::Error { } /// Labels to apply to this Diagnostic's [Diagnostic::source_code] - fn labels<'a>(&'a self) -> Option + 'a>> { + fn labels<'a>(&'a self) -> Option + 'a>> { None } } @@ -162,7 +162,7 @@ pub enum Severity { } /** -Represents a readable source of some sort. +Represents readable source code of some sort. This trait is able to support simple Source types like [String]s, as well as more involved types like indexes into centralized `SourceMap`-like types, @@ -170,7 +170,7 @@ file handles, and even network streams. If you can read it, you can source it, and it's not necessary to read the whole thing--meaning you should be able to -support Sources which are gigabytes or larger in size. +support SourceCodes which are gigabytes or larger in size. */ pub trait SourceCode: std::fmt::Debug + Send + Sync { /// Read the bytes for a specific span from this SourceCode, keeping a @@ -188,6 +188,42 @@ pub trait SourceCode: std::fmt::Debug + Send + Sync { } } +/** +A labeled [SourceSpan]. +*/ +#[derive(Debug, Clone)] +pub struct LabeledSpan { + label: Option, + span: SourceSpan, +} + +impl LabeledSpan { + /// Gets the (optional) label string for this LabeledSpan. + pub fn label(&self) -> Option<&str> { + self.label.as_deref() + } + + /// Returns a reference to the inner [SourceSpan]. + pub fn inner(&self) -> &SourceSpan { + &self.span + } + + /// Returns the 0-based starting byte offset. + pub fn offset(&self) -> usize { + self.span.offset() + } + + /// Returns the number of bytes this LabeledSpan spans. + pub fn len(&self) -> usize { + self.span.len() + } + + /// True if this LabeledSpan is empty. + pub fn is_empty(&self) -> bool { + self.span.is_empty() + } +} + /** Contents of a [SourceCode] covered by [SourceSpan]. From d11d745eb0fda7a87e86b85cbdf6e6abe202c622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Sat, 18 Sep 2021 22:40:38 -0700 Subject: [PATCH 06/14] wip stuff --- miette-derive/src/lib.rs | 2 +- src/named_source.rs | 4 ++-- src/protocol.rs | 4 ++-- src/source_impls.rs | 11 +++++++++++ tests/derive.rs | 33 +++------------------------------ tests/narrated.rs | 15 +++++---------- 6 files changed, 24 insertions(+), 45 deletions(-) diff --git a/miette-derive/src/lib.rs b/miette-derive/src/lib.rs index c99f8138..a135a14d 100644 --- a/miette-derive/src/lib.rs +++ b/miette-derive/src/lib.rs @@ -14,7 +14,7 @@ mod snippets; mod url; mod utils; -#[proc_macro_derive(Diagnostic, attributes(diagnostic, snippet, highlight))] +#[proc_macro_derive(Diagnostic, attributes(diagnostic, label, source_code))] pub fn derive_diagnostic(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); let cmd = match Diagnostic::from_derive_input(input) { diff --git a/src/named_source.rs b/src/named_source.rs index 484724f5..1949a552 100644 --- a/src/named_source.rs +++ b/src/named_source.rs @@ -1,4 +1,4 @@ -use crate::SourceCode; +use crate::{SourceCode, SourceSpan}; /// Utility struct for when you have a regular [Source] type, such as a String, /// that doesn't implement `name`, or if you want to override the `.name()` @@ -35,7 +35,7 @@ impl SourceCode for NamedSource { .read_span(span, context_lines_before, context_lines_after) } - fn name(&self) -> Option { + fn name(&self, span: &SourceSpan) -> Option { Some(self.name.clone()) } } diff --git a/src/protocol.rs b/src/protocol.rs index bbe32a42..9f01f2df 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -182,8 +182,8 @@ pub trait SourceCode: std::fmt::Debug + Send + Sync { context_lines_after: usize, ) -> Result, MietteError>; - /// Optional name, usually a filename, for this source. - fn name(&self) -> Option { + /// Optional name, usually a filename, where a [SourceSpan] is located in the SourceCode. + fn name(&self, span: &SourceSpan) -> Option { None } } diff --git a/src/source_impls.rs b/src/source_impls.rs index 64909880..157c6bcb 100644 --- a/src/source_impls.rs +++ b/src/source_impls.rs @@ -100,6 +100,17 @@ impl SourceCode for [u8] { } } +impl<'src> SourceCode for &'src [u8] { + fn read_span<'a>( + &'a self, + span: &SourceSpan, + context_lines_before: usize, + context_lines_after: usize, + ) -> Result, MietteError> { + <[u8] as SourceCode>::read_span(self, span, context_lines_before, context_lines_after) + } +} + impl SourceCode for str { fn read_span<'a>( &'a self, diff --git a/tests/derive.rs b/tests/derive.rs index 44a8827f..d6dfb37a 100644 --- a/tests/derive.rs +++ b/tests/derive.rs @@ -202,39 +202,12 @@ fn test_snippet_named_struct() { #[error("welp")] #[diagnostic(code(foo::bar::baz))] struct Foo { - // The actual "source code" our contexts will be using. This can be - // reused by multiple contexts, and just needs to implement - // miette::Source! + #[source_code] src: String, - - // The "snippet" span. This is the span that will be displayed to - // users. It should be a big enough slice of the Source to provide - // reasonable context, but still somewhat compact. - // - // You can have as many of these #[snippet] fields as you want, and - // even feed them from different sources! - // - // Example display: - // / [my_snippet]: hi this is where the thing went wrong. - // 1 | hello - // 2 | world - #[snippet(src, message("hi this is where the thing went wrong"))] - snip: SourceSpan, // Defines filename using `label` - - // "Highlights" are the specific highlights _inside_ the snippet. - // These will be used to underline/point to specific sections of the - // #[snippet] they refer to. As such, these SourceSpans must be within - // the bounds of their referenced snippet. - // - // Example display: - // 1 | var1 + var2 - // | ^^^^ ^^^^ - var 2 - // | | - // | var 1 - #[highlight(snip)] + #[label("var 1")] // label from SourceSpan is used, if any. var1: SourceSpan, - #[highlight(snip)] + #[label("var 2")] // Anything that's Clone + Into can be used here. var2: (usize, usize), } diff --git a/tests/narrated.rs b/tests/narrated.rs index 041f0f67..c9224ce4 100644 --- a/tests/narrated.rs +++ b/tests/narrated.rs @@ -1,7 +1,4 @@ -use miette::{ - Diagnostic, MietteError, NamedSource, - NarratableReportHandler, Report, SourceSpan, -}; +use miette::{Diagnostic, MietteError, NamedSource, NarratableReportHandler, Report, SourceSpan}; #[cfg(feature = "fancy")] use miette::{GraphicalReportHandler, GraphicalTheme}; @@ -30,19 +27,17 @@ fn single_line_highlight() -> Result<(), MietteError> { #[error("oops!")] #[diagnostic(code(oops::my::bad), help("try doing it better next time?"))] struct MyBad { + #[source_code] src: NamedSource, - #[snippet(src, message("This is the part that broke"))] - ctx: SourceSpan, - #[highlight(ctx, label = "this bit here")] - highlight: SourceSpan, + #[label("this bit here")] + bad_thing: SourceSpan, } let src = "source\n text\n here".to_string(); let len = src.len(); let err = MyBad { src: NamedSource::new("bad_file.rs", src), - ctx: (0, len).into(), - highlight: (9, 4).into(), + bad_thing: (9, 4).into(), }; let out = fmt_report(err.into()); println!("{}", out); From a39d4135c1c09ed88407e0a634196d0e0cb32790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Sun, 19 Sep 2021 10:43:36 -0700 Subject: [PATCH 07/14] comment out the snippet renderer again --- src/handlers/graphical.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/handlers/graphical.rs b/src/handlers/graphical.rs index 2d5a089e..4c4aa411 100644 --- a/src/handlers/graphical.rs +++ b/src/handlers/graphical.rs @@ -94,20 +94,12 @@ impl GraphicalReportHandler { writeln!(f)?; self.render_causes(f, diagnostic)?; - if let Some(source) = diagnostic.source_code() { - if let Some(labels) = diagnostic.labels() { - let mut labels = labels.collect::>(); - labels.sort_unstable_by_key(|l| l.inner().offset()); - if !labels.is_empty() { - writeln!(f)?; - self.render_snippets(f, source, labels)?; - } - } - } - // if let Some(snippets) = diagnostic.snippets() { - // for snippet in snippets { + // if let Some(labels) = diagnostic.labels() { + // let mut labels = labels.collect::>(); + // labels.sort_unstable_by_key(|l| l.inner().offset()); + // if !labels.is_empty() { // writeln!(f)?; - // self.render_snippet(f, &snippet)?; + // self.render_snippets(f, labels)?; // } // } @@ -238,6 +230,7 @@ impl GraphicalReportHandler { Ok(()) } + /* fn render_snippets( &self, f: &mut impl fmt::Write, @@ -623,6 +616,7 @@ impl GraphicalReportHandler { } Ok((context_data, lines)) } + */ } impl ReportHandler for GraphicalReportHandler { From 261b45fe466e7e3df7efcac33293bec12fa8ae1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Sun, 19 Sep 2021 10:44:00 -0700 Subject: [PATCH 08/14] I don't know what just happened but it feels great --- src/named_source.rs | 23 +++++++++++++---------- src/protocol.rs | 43 ++++++++++++++++++++++++++++++++----------- src/source_impls.rs | 14 +++++++------- 3 files changed, 52 insertions(+), 28 deletions(-) diff --git a/src/named_source.rs b/src/named_source.rs index 1949a552..fe9a9fdd 100644 --- a/src/named_source.rs +++ b/src/named_source.rs @@ -1,4 +1,4 @@ -use crate::{SourceCode, SourceSpan}; +use crate::{MietteError, MietteSpanContents, SourceCode, SpanContents}; /// Utility struct for when you have a regular [Source] type, such as a String, /// that doesn't implement `name`, or if you want to override the `.name()` @@ -10,7 +10,7 @@ pub struct NamedSource { } impl NamedSource { - /// Create a new [NamedSource] using a regular [Source] and giving it a [Source::name]. + /// Create a new [NamedSource] using a regular [SourceCode] and giving its returned [SpanContents] a name. pub fn new(name: impl AsRef, source: impl SourceCode + Send + Sync + 'static) -> Self { Self { source: Box::new(source), @@ -18,7 +18,7 @@ impl NamedSource { } } - /// Returns a reference the inner [Source] type for this [NamedSource]. + /// Returns a reference the inner [SourceCode] type for this [NamedSource]. pub fn inner(&self) -> &(dyn SourceCode + Send + Sync + 'static) { &*self.source } @@ -30,12 +30,15 @@ impl SourceCode for NamedSource { span: &crate::SourceSpan, context_lines_before: usize, context_lines_after: usize, - ) -> Result, crate::MietteError> { - self.source - .read_span(span, context_lines_before, context_lines_after) - } - - fn name(&self, span: &SourceSpan) -> Option { - Some(self.name.clone()) + ) -> Result + 'a>, MietteError> { + let contents = self + .inner() + .read_span(span, context_lines_before, context_lines_after)?; + Ok(Box::new(MietteSpanContents::new_named( + &self.name, + contents.data(), + contents.line(), + contents.column(), + ))) } } diff --git a/src/protocol.rs b/src/protocol.rs index 9f01f2df..484cc03a 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -180,12 +180,7 @@ pub trait SourceCode: std::fmt::Debug + Send + Sync { span: &SourceSpan, context_lines_before: usize, context_lines_after: usize, - ) -> Result, MietteError>; - - /// Optional name, usually a filename, where a [SourceSpan] is located in the SourceCode. - fn name(&self, span: &SourceSpan) -> Option { - None - } + ) -> Result + 'a>, MietteError>; } /** @@ -229,9 +224,13 @@ Contents of a [SourceCode] covered by [SourceSpan]. Includes line and column information to optimize highlight calculations. */ -pub trait SpanContents { +pub trait SpanContents<'a> { /// Reference to the data inside the associated span, in bytes. - fn data(&self) -> &[u8]; + fn data(&self) -> &'a [u8]; + /// An optional (file?) name for the container of this SpanContents. + fn name(&self) -> Option<&'a 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, @@ -250,17 +249,39 @@ pub struct MietteSpanContents<'a> { line: usize, // The 0-indexed column where the associated [SourceSpan] _starts_. column: usize, + // Optional filename + name: Option<&'a str>, } impl<'a> MietteSpanContents<'a> { /// Make a new [MietteSpanContents] object. pub fn new(data: &'a [u8], line: usize, column: usize) -> MietteSpanContents<'a> { - MietteSpanContents { data, line, column } + MietteSpanContents { + data, + line, + column, + name: None, + } + } + + /// Make a new [MietteSpanContents] object, with a name for its "file". + pub fn new_named( + name: &'a str, + data: &'a [u8], + line: usize, + column: usize, + ) -> MietteSpanContents<'a> { + MietteSpanContents { + data, + line, + column, + name: Some(name), + } } } -impl<'a> SpanContents for MietteSpanContents<'a> { - fn data(&self) -> &[u8] { +impl<'a> SpanContents<'a> for MietteSpanContents<'a> { + fn data(&self) -> &'a [u8] { self.data } fn line(&self) -> usize { diff --git a/src/source_impls.rs b/src/source_impls.rs index 157c6bcb..fccab2b4 100644 --- a/src/source_impls.rs +++ b/src/source_impls.rs @@ -89,7 +89,7 @@ impl SourceCode for [u8] { span: &SourceSpan, context_lines_before: usize, context_lines_after: usize, - ) -> Result, MietteError> { + ) -> Result + 'a>, MietteError> { let (data, start_line, start_column) = context_info(self, span, context_lines_before, context_lines_after)?; return Ok(Box::new(MietteSpanContents::new( @@ -106,7 +106,7 @@ impl<'src> SourceCode for &'src [u8] { span: &SourceSpan, context_lines_before: usize, context_lines_after: usize, - ) -> Result, MietteError> { + ) -> Result + 'a>, MietteError> { <[u8] as SourceCode>::read_span(self, span, context_lines_before, context_lines_after) } } @@ -117,7 +117,7 @@ impl SourceCode for str { span: &SourceSpan, context_lines_before: usize, context_lines_after: usize, - ) -> Result, MietteError> { + ) -> Result + 'a>, MietteError> { <[u8] as SourceCode>::read_span( self.as_bytes(), span, @@ -134,7 +134,7 @@ impl<'s> SourceCode for &'s str { span: &SourceSpan, context_lines_before: usize, context_lines_after: usize, - ) -> Result, MietteError> { + ) -> Result + 'a>, MietteError> { ::read_span(self, span, context_lines_before, context_lines_after) } } @@ -145,7 +145,7 @@ impl SourceCode for String { span: &SourceSpan, context_lines_before: usize, context_lines_after: usize, - ) -> Result, MietteError> { + ) -> Result + 'a>, MietteError> { ::read_span(self, span, context_lines_before, context_lines_after) } } @@ -156,7 +156,7 @@ impl SourceCode for Arc { span: &SourceSpan, context_lines_before: usize, context_lines_after: usize, - ) -> Result, MietteError> { + ) -> Result + 'a>, MietteError> { self.as_ref() .read_span(span, context_lines_before, context_lines_after) } @@ -173,7 +173,7 @@ where span: &SourceSpan, context_lines_before: usize, context_lines_after: usize, - ) -> Result, MietteError> { + ) -> Result + 'a>, MietteError> { self.as_ref() .read_span(span, context_lines_before, context_lines_after) } From 873397c30e8cf9fed43aca834c7f05083276225f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Sun, 19 Sep 2021 12:39:49 -0700 Subject: [PATCH 09/14] mise en place --- src/handlers/graphical.rs | 66 +++++++++++++++++++++++++-------------- src/named_source.rs | 16 +++++++--- src/protocol.rs | 14 +++++++-- 3 files changed, 66 insertions(+), 30 deletions(-) diff --git a/src/handlers/graphical.rs b/src/handlers/graphical.rs index 4c4aa411..a960fc48 100644 --- a/src/handlers/graphical.rs +++ b/src/handlers/graphical.rs @@ -1,5 +1,6 @@ use std::fmt::{self, Write}; +use itertools::Itertools; use owo_colors::{OwoColorize, Style}; use unicode_width::UnicodeWidthStr; @@ -94,14 +95,16 @@ impl GraphicalReportHandler { writeln!(f)?; self.render_causes(f, diagnostic)?; - // if let Some(labels) = diagnostic.labels() { - // let mut labels = labels.collect::>(); - // labels.sort_unstable_by_key(|l| l.inner().offset()); - // if !labels.is_empty() { - // writeln!(f)?; - // self.render_snippets(f, labels)?; - // } - // } + if let Some(source) = diagnostic.source_code() { + if let Some(labels) = diagnostic.labels() { + let mut labels = labels.collect::>(); + labels.sort_unstable_by_key(|l| l.inner().offset()); + if !labels.is_empty() { + writeln!(f)?; + self.render_snippets(f, source, labels)?; + } + } + } self.render_footer(f, diagnostic)?; Ok(()) @@ -230,13 +233,30 @@ impl GraphicalReportHandler { Ok(()) } - /* fn render_snippets( &self, f: &mut impl fmt::Write, source: &dyn SourceCode, labels: Vec, ) -> fmt::Result { + // TODO: Actually do the rewrite against the new protocol. + let contexts: Vec<_> = 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, + )) + } else { + Err((left, right)) + } + }) + .collect(); let (contents, lines) = self.get_lines(source, &labels)?; // sorting is your friend @@ -278,18 +298,19 @@ impl GraphicalReportHandler { self.theme.characters.ltop, self.theme.characters.hbar, )?; - 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)?; - } + // 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)?; + // } // Blank line to improve readability writeln!( @@ -554,7 +575,7 @@ impl GraphicalReportHandler { &'a self, source: &'a dyn SourceCode, labels: &'a [LabeledSpan], - ) -> Result<(Box, Vec), fmt::Error> { + ) -> Result<(Box + 'a>, Vec), 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 = ( @@ -616,7 +637,6 @@ impl GraphicalReportHandler { } Ok((context_data, lines)) } - */ } impl ReportHandler for GraphicalReportHandler { diff --git a/src/named_source.rs b/src/named_source.rs index fe9a9fdd..656cc3d9 100644 --- a/src/named_source.rs +++ b/src/named_source.rs @@ -3,12 +3,20 @@ use crate::{MietteError, MietteSpanContents, SourceCode, SpanContents}; /// Utility struct for when you have a regular [Source] type, such as a String, /// that doesn't implement `name`, or if you want to override the `.name()` /// returned by the `Source`. -#[derive(Debug)] pub struct NamedSource { - source: Box, + source: Box, name: String, } +impl std::fmt::Debug for NamedSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("NamedSource") + .field("name", &self.name) + .field("source", &""); + Ok(()) + } +} + impl NamedSource { /// Create a new [NamedSource] using a regular [SourceCode] and giving its returned [SpanContents] a name. pub fn new(name: impl AsRef, source: impl SourceCode + Send + Sync + 'static) -> Self { @@ -19,7 +27,7 @@ impl NamedSource { } /// Returns a reference the inner [SourceCode] type for this [NamedSource]. - pub fn inner(&self) -> &(dyn SourceCode + Send + Sync + 'static) { + pub fn inner(&self) -> &(dyn SourceCode + 'static) { &*self.source } } @@ -35,7 +43,7 @@ impl SourceCode for NamedSource { .inner() .read_span(span, context_lines_before, context_lines_after)?; Ok(Box::new(MietteSpanContents::new_named( - &self.name, + self.name.clone(), contents.data(), contents.line(), contents.column(), diff --git a/src/protocol.rs b/src/protocol.rs index 484cc03a..28e2b5b9 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -172,7 +172,7 @@ If you can read it, you can source it, and it's not necessary to read the whole thing--meaning you should be able to support SourceCodes which are gigabytes or larger in size. */ -pub trait SourceCode: std::fmt::Debug + Send + Sync { +pub trait SourceCode { /// Read the bytes for a specific span from this SourceCode, keeping a /// certain number of lines before and after the span as context. fn read_span<'a>( @@ -193,6 +193,14 @@ pub struct LabeledSpan { } impl LabeledSpan { + /// Makes a new labels span. + pub fn new(label: Option, offset: ByteOffset, len: ByteOffset) -> Self { + Self { + label, + span: (offset, len).into(), + } + } + /// Gets the (optional) label string for this LabeledSpan. pub fn label(&self) -> Option<&str> { self.label.as_deref() @@ -250,7 +258,7 @@ pub struct MietteSpanContents<'a> { // The 0-indexed column where the associated [SourceSpan] _starts_. column: usize, // Optional filename - name: Option<&'a str>, + name: Option, } impl<'a> MietteSpanContents<'a> { @@ -266,7 +274,7 @@ impl<'a> MietteSpanContents<'a> { /// Make a new [MietteSpanContents] object, with a name for its "file". pub fn new_named( - name: &'a str, + name: String, data: &'a [u8], line: usize, column: usize, From a4922454f58b0bbab279fbceb92424a7d73f4847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Sun, 19 Sep 2021 16:50:28 -0700 Subject: [PATCH 10/14] labels macro attribute --- miette-derive/src/diagnostic.rs | 18 +++- miette-derive/src/label.rs | 177 ++++++++++++++++++++++++++++++++ miette-derive/src/lib.rs | 1 + src/protocol.rs | 2 +- 4 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 miette-derive/src/label.rs diff --git a/miette-derive/src/diagnostic.rs b/miette-derive/src/diagnostic.rs index 8bf747e7..e6b3a769 100644 --- a/miette-derive/src/diagnostic.rs +++ b/miette-derive/src/diagnostic.rs @@ -6,6 +6,7 @@ use crate::code::Code; use crate::diagnostic_arg::DiagnosticArg; use crate::forward::{Forward, WhichFn}; use crate::help::Help; +use crate::label::Labels; use crate::severity::Severity; use crate::snippets::Snippets; use crate::url::Url; @@ -32,7 +33,7 @@ pub struct DiagnosticDef { pub enum DiagnosticDefArgs { Transparent(Forward), - Concrete(DiagnosticConcreteArgs), + Concrete(Box), } impl DiagnosticDefArgs { @@ -59,6 +60,7 @@ pub struct DiagnosticConcreteArgs { pub code: Option, pub severity: Option, pub help: Option, + pub labels: Option, pub snippets: Option, pub url: Option, pub forward: Option, @@ -100,11 +102,13 @@ impl DiagnosticConcreteArgs { } } let snippets = Snippets::from_fields(fields)?; + let labels = Labels::from_fields(fields)?; let concrete = DiagnosticConcreteArgs { code, help, severity, snippets, + labels, url, forward, }; @@ -141,7 +145,7 @@ impl DiagnosticDefArgs { .into_iter() .filter(|x| !matches!(x, DiagnosticArg::Transparent)); let concrete = DiagnosticConcreteArgs::parse(ident, fields, attr, args)?; - Ok(DiagnosticDefArgs::Concrete(concrete)) + Ok(DiagnosticDefArgs::Concrete(Box::new(concrete))) } } @@ -208,6 +212,7 @@ impl Diagnostic { let code_method = forward.gen_struct_method(WhichFn::Code); let help_method = forward.gen_struct_method(WhichFn::Help); let url_method = forward.gen_struct_method(WhichFn::Url); + let labels_method = forward.gen_struct_method(WhichFn::Labels); let severity_method = forward.gen_struct_method(WhichFn::Severity); let snippets_method = forward.gen_struct_method(WhichFn::Labels); @@ -216,6 +221,7 @@ impl Diagnostic { #code_method #help_method #url_method + #labels_method #severity_method #snippets_method } @@ -253,6 +259,11 @@ impl Diagnostic { .as_ref() .and_then(|x| x.gen_struct(ident, fields)) .or_else(|| forward(WhichFn::Url)); + let labels_body = concrete + .labels + .as_ref() + .and_then(|x| x.gen_struct(fields)) + .or_else(|| forward(WhichFn::Url)); quote! { impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause { @@ -261,6 +272,7 @@ impl Diagnostic { #sev_body #snip_body #url_body + #labels_body } } } @@ -276,6 +288,7 @@ impl Diagnostic { let help_body = Help::gen_enum(variants); let sev_body = Severity::gen_enum(variants); let snip_body = Snippets::gen_enum(variants); + let labels_body = Labels::gen_enum(variants); let url_body = Url::gen_enum(ident, variants); quote! { impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause { @@ -283,6 +296,7 @@ impl Diagnostic { #help_body #sev_body #snip_body + #labels_body #url_body } } diff --git a/miette-derive/src/label.rs b/miette-derive/src/label.rs new file mode 100644 index 00000000..987bcaf1 --- /dev/null +++ b/miette-derive/src/label.rs @@ -0,0 +1,177 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{ + parenthesized, + parse::{Parse, ParseStream}, + spanned::Spanned, + Token, +}; + +use crate::{ + diagnostic::{DiagnosticConcreteArgs, DiagnosticDef}, + fmt::{self, Display}, + forward::WhichFn, + utils::{display_pat_members, gen_all_variants_with}, +}; + +pub struct Labels(Vec