Skip to content

Commit

Permalink
feat(protocol): new SourceSpans with labels
Browse files Browse the repository at this point in the history
  • Loading branch information
zkat committed Aug 17, 2021
1 parent 36b86df commit acfeb9c
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 133 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ fn pretend_this_is_main() -> Result<(), MyBad> {
Err(MyBad {
src: Arc::new(src),
filename: "bad_file.rs".into(),
snip: (0, (len - 1)).into(),
bad_bit: (9, 12).into(),
snip: (0, len).into(),
bad_bit: (9, 3).into(),
})
}
```
Expand Down
72 changes: 9 additions & 63 deletions miette-derive/src/snippets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,21 @@ pub struct Snippets(Vec<Snippet>);
struct Snippet {
message: Option<MemberOrString>,
highlights: Vec<Highlight>,
source_name: MemberOrString,
source: syn::Member,
snippet: syn::Member,
}

struct Highlight {
highlight: syn::Member,
label: Option<MemberOrString>,
}

struct SnippetAttr {
source: syn::Member,
source_name: MemberOrString,
message: Option<MemberOrString>,
}

struct HighlightAttr {
snippet: syn::Member,
label: Option<MemberOrString>,
}

enum MemberOrString {
Expand Down Expand Up @@ -82,15 +78,8 @@ impl Parse for SnippetAttr {
))
}
};
let src_name = iter
.next()
.ok_or_else(|| syn::Error::new(span, "Expected a source name."))?;
let message = iter.next();
Ok(SnippetAttr {
source,
source_name: src_name,
message,
})
Ok(SnippetAttr { source, message })
}
}

Expand All @@ -107,8 +96,7 @@ impl Parse for HighlightAttr {
"must be an identifier that refers to something with a #[snippet] attribute.",
)),
};
let label = iter.next();
Ok(HighlightAttr { snippet, label })
Ok(HighlightAttr { snippet })
}
}

Expand Down Expand Up @@ -137,18 +125,13 @@ impl Snippets {
span: field.span(),
})
};
let SnippetAttr {
source,
message,
source_name,
} = attr.parse_args::<SnippetAttr>()?;
let SnippetAttr { source, message } = attr.parse_args::<SnippetAttr>()?;
// TODO: useful error when source refers to a field that doesn't exist.
snippets.insert(
snippet.clone(),
Snippet {
message,
highlights: Vec::new(),
source_name,
source,
snippet,
},
Expand All @@ -160,7 +143,7 @@ impl Snippets {
for (i, field) in fields.iter().enumerate() {
for attr in &field.attrs {
if attr.path.is_ident("highlight") {
let HighlightAttr { snippet, label } = attr.parse_args::<HighlightAttr>()?;
let HighlightAttr { snippet } = attr.parse_args::<HighlightAttr>()?;
if let Some(snippet) = snippets.get_mut(&snippet) {
let member = if let Some(ident) = field.ident.clone() {
syn::Member::Named(ident)
Expand All @@ -170,10 +153,7 @@ impl Snippets {
span: field.span(),
})
};
snippet.highlights.push(Highlight {
label,
highlight: member,
});
snippet.highlights.push(Highlight { highlight: member });
} else {
return Err(syn::Error::new(snippet.span(), "Highlight must refer to an existing field with a #[snippet(...)] attribute."));
}
Expand Down Expand Up @@ -218,18 +198,6 @@ impl Snippets {
source: self.#src_ident.clone(),
};

// Source name
let src_name = match &snippet.source_name {
MemberOrString::String(str) => {
quote! {
source_name: #str.into(),
}
}
MemberOrString::Member(member) => quote! {
source_name: self.#member.clone(),
},
};

// Context
let context = &snippet.snippet;
let context = quote! {
Expand All @@ -238,9 +206,9 @@ impl Snippets {

// Highlights
let highlights = snippet.highlights.iter().map(|highlight| {
let Highlight { highlight, label } = highlight;
let Highlight { highlight } = highlight;
quote! {
(#label.into(), self.#highlight.clone())
self.#highlight.clone()
}
});
let highlights = quote! {
Expand All @@ -253,7 +221,6 @@ impl Snippets {
quote! {
miette::DiagnosticSnippet {
#msg
#src_name
#src_ident
#context
#highlights
Expand Down Expand Up @@ -314,26 +281,6 @@ impl Snippets {
source: #src_ident.clone(),
};

// Source name
let src_name = match &snippet.source_name {
MemberOrString::String(str) => {
quote! {
source_name: #str.into(),
}
}
MemberOrString::Member(m) => {
let m = match m {
syn::Member::Named(id) => id.clone(),
syn::Member::Unnamed(syn::Index { index, .. }) => {
format_ident!("_{}", index)
}
};
quote! {
source_name: #m.clone(),
}
}
};

// Context
let context = match &snippet.snippet {
syn::Member::Named(id) => id.clone(),
Expand All @@ -347,15 +294,15 @@ impl Snippets {

// Highlights
let highlights = snippet.highlights.iter().map(|highlight| {
let Highlight { highlight, label } = highlight;
let Highlight { highlight } = highlight;
let m = match highlight {
syn::Member::Named(id) => id.clone(),
syn::Member::Unnamed(syn::Index { index, .. }) => {
format_ident!("_{}", index)
}
};
quote! {
(#label.into(), #m.clone())
#m.clone()
}
});
let highlights = quote! {
Expand All @@ -368,7 +315,6 @@ impl Snippets {
quote! {
miette::DiagnosticSnippet {
#msg
#src_name
#src_ident
#context
#highlights
Expand Down
62 changes: 45 additions & 17 deletions src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,52 +174,80 @@ A snippet from a [Source] to be displayed with a message and possibly some highl
pub struct DiagnosticSnippet {
/// Explanation of this specific diagnostic snippet.
pub message: Option<String>,
/// The "filename" for this snippet.
pub source_name: String,
/// A [Source] that can be used to read the actual text of a source.
pub source: Arc<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<Vec<(String, SourceSpan)>>,
pub highlights: Option<Vec<SourceSpan>>,
}

/**
Span within a [Source] with an associated message.
*/
#[derive(Clone, Debug)]
pub struct SourceSpan {
/// An optional label for this span. Rendered differently depending on
/// context.
label: Option<String>,
/// The start of the span.
pub start: SourceOffset,
/// The (exclusive) end of the span.
pub end: SourceOffset,
offset: SourceOffset,
/// The total length of the span. Think of this as an offset from `start`.
length: SourceOffset,
}

impl SourceSpan {
pub fn new(start: SourceOffset, end: SourceOffset) -> Self {
assert!(
start.offset() <= end.offset(),
"Starting offset must come before the end offset."
);
Self { start, end }
pub fn new(start: SourceOffset, length: SourceOffset) -> Self {
Self {
label: None,
offset: start,
length,
}
}

pub fn new_labeled(label: impl AsRef<str>, start: SourceOffset, length: SourceOffset) -> Self {
Self {
label: Some(label.as_ref().into()),
offset: start,
length,
}
}

pub fn offset(&self) -> usize {
self.offset.offset()
}

pub fn label(&self) -> Option<&str> {
self.label.as_ref().map(|x| &x[..])
}

pub fn len(&self) -> usize {
self.end.offset() - self.start.offset() + 1
self.length.offset()
}

pub fn is_empty(&self) -> bool {
self.start.offset() == self.end.offset()
self.length.offset() == 0
}
}

impl From<(ByteOffset, ByteOffset)> for SourceSpan {
fn from((start, end): (ByteOffset, ByteOffset)) -> Self {
fn from((start, len): (ByteOffset, ByteOffset)) -> Self {
Self {
label: None,
offset: start.into(),
length: len.into(),
}
}
}

impl<T: AsRef<str>> From<(T, ByteOffset, ByteOffset)> for SourceSpan {
fn from((label, start, len): (T, ByteOffset, ByteOffset)) -> Self {
Self {
start: start.into(),
end: end.into(),
label: Some(label.as_ref().into()),
offset: start.into(),
length: len.into(),
}
}
}
Expand Down
22 changes: 13 additions & 9 deletions src/reporter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ impl MietteReporter {
snippet: &DiagnosticSnippet,
) -> fmt::Result {
use fmt::Write as _;
write!(f, "[{}]", snippet.source_name)?;
if let Some(source_name) = snippet.context.label() {
write!(f, "[{}]", source_name)?;
}
if let Some(msg) = &snippet.message {
write!(f, " {}:", msg)?;
}
Expand All @@ -36,7 +38,7 @@ impl MietteReporter {
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.start.offset();
let mut offset = snippet.context.offset();
let mut line_offset = offset;
let mut iter = context.chars().peekable();
let mut line_str = String::new();
Expand Down Expand Up @@ -71,20 +73,22 @@ impl MietteReporter {
writeln!(indented(f), "{: <2} | {}", line, line_str)?;
line_str.clear();
if let Some(highlights) = highlights {
for (label, span) in highlights {
if span.start.offset() >= line_offset && span.end.offset() < offset {
for span in highlights {
if span.offset() >= line_offset && (span.offset() + span.len()) < offset {
// Highlight only covers one line.
write!(indented(f), "{: <2} | ", "⫶")?;
write!(
f,
"{}{} ",
" ".repeat(span.start.offset() - line_offset),
" ".repeat(span.offset() - line_offset),
"^".repeat(span.len())
)?;
writeln!(f, "{}", label)?;
} else if span.start.offset() < offset
&& span.start.offset() >= line_offset
&& span.end.offset() >= offset
if let Some(label) = span.label() {
writeln!(f, "{}", label)?;
}
} else if span.offset() < offset
&& span.offset() >= line_offset
&& (span.offset() + span.len()) >= offset
{
// Multiline highlight.
todo!("Multiline highlights.");
Expand Down

0 comments on commit acfeb9c

Please sign in to comment.