diff --git a/benches/simple.rs b/benches/simple.rs index 4eacfda..fccb70b 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -4,7 +4,7 @@ extern crate criterion; use criterion::{black_box, Criterion}; -use annotate_snippets::{Label, Renderer, Slice, Snippet}; +use annotate_snippets::{Level, Renderer, Snippet}; fn create_snippet(renderer: Renderer) { let source = r#") -> Option { @@ -29,16 +29,23 @@ fn create_snippet(renderer: Renderer) { _ => continue, } }"#; - let snippet = Snippet::error("mismatched types").id("E0308").slice( - Slice::new(source, 51) + let message = Level::Error.title("mismatched types").id("E0308").snippet( + Snippet::source(source) + .line_start(51) .origin("src/format.rs") .annotation( - Label::warning("expected `Option` because of return type").span(5..19), + Level::Warning + .span(5..19) + .label("expected `Option` because of return type"), ) - .annotation(Label::error("expected enum `std::option::Option`").span(26..724)), + .annotation( + Level::Error + .span(26..724) + .label("expected enum `std::option::Option`"), + ), ); - let _result = renderer.render(snippet).to_string(); + let _result = renderer.render(message).to_string(); } pub fn criterion_benchmark(c: &mut Criterion) { diff --git a/examples/expected_type.rs b/examples/expected_type.rs index adcbeff..0184dee 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -1,23 +1,23 @@ -use annotate_snippets::{Label, Renderer, Slice, Snippet}; +use annotate_snippets::{Level, Renderer, Snippet}; fn main() { let source = r#" annotations: vec![SourceAnnotation { label: "expected struct `annotate_snippets::snippet::Slice`, found reference" , range: <22, 25>,"#; - let snippet = Snippet::error("expected type, found `22`").slice( - Slice::new(source, 26) + let message = Level::Error.title("expected type, found `22`").snippet( + Snippet::source(source) + .line_start(26) .origin("examples/footer.rs") .fold(true) .annotation( - Label::error( - "expected struct `annotate_snippets::snippet::Slice`, found reference", - ) - .span(193..195), + Level::Error + .span(193..195) + .label("expected struct `annotate_snippets::snippet::Slice`, found reference"), ) - .annotation(Label::info("while parsing this struct").span(34..50)), + .annotation(Level::Info.span(34..50).label("while parsing this struct")), ); let renderer = Renderer::styled(); - anstream::println!("{}", renderer.render(snippet)); + anstream::println!("{}", renderer.render(message)); } diff --git a/examples/footer.rs b/examples/footer.rs index 6c45328..8b4d078 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -1,22 +1,22 @@ -use annotate_snippets::{Label, Renderer, Slice, Snippet}; +use annotate_snippets::{Label, Level, Renderer, Snippet}; fn main() { - let snippet = Snippet::error("mismatched types") - .id("E0308") - .slice( - Slice::new(" slices: vec![\"A\",", 13) - .origin("src/multislice.rs") - .annotation( - Label::error( + let message = + Level::Error + .title("mismatched types") + .id("E0308") + .snippet( + Snippet::source(" slices: vec![\"A\",") + .line_start(13) + .origin("src/multislice.rs") + .annotation(Level::Error.span(21..24).label( "expected struct `annotate_snippets::snippet::Slice`, found reference", - ) - .span(21..24), - ), - ) - .footer(Label::note( - "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`", - )); + )), + ) + .footer(Label::note( + "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`", + )); let renderer = Renderer::styled(); - anstream::println!("{}", renderer.render(snippet)); + anstream::println!("{}", renderer.render(message)); } diff --git a/examples/format.rs b/examples/format.rs index 1337812..1606777 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{Label, Renderer, Slice, Snippet}; +use annotate_snippets::{Level, Renderer, Snippet}; fn main() { let source = r#") -> Option { @@ -23,15 +23,22 @@ fn main() { _ => continue, } }"#; - let snippet = Snippet::error("mismatched types").id("E0308").slice( - Slice::new(source, 51) + let message = Level::Error.title("mismatched types").id("E0308").snippet( + Snippet::source(source) + .line_start(51) .origin("src/format.rs") .annotation( - Label::warning("expected `Option` because of return type").span(5..19), + Level::Warning + .span(5..19) + .label("expected `Option` because of return type"), ) - .annotation(Label::error("expected enum `std::option::Option`").span(26..724)), + .annotation( + Level::Error + .span(26..724) + .label("expected enum `std::option::Option`"), + ), ); let renderer = Renderer::styled(); - anstream::println!("{}", renderer.render(snippet)); + anstream::println!("{}", renderer.render(message)); } diff --git a/examples/multislice.rs b/examples/multislice.rs index b6fcb3f..ea31bbd 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -1,10 +1,19 @@ -use annotate_snippets::{Renderer, Slice, Snippet}; +use annotate_snippets::{Level, Renderer, Snippet}; fn main() { - let snippet = Snippet::error("mismatched types") - .slice(Slice::new("Foo", 51).origin("src/format.rs")) - .slice(Slice::new("Faa", 129).origin("src/display.rs")); + let message = Level::Error + .title("mismatched types") + .snippet( + Snippet::source("Foo") + .line_start(51) + .origin("src/format.rs"), + ) + .snippet( + Snippet::source("Faa") + .line_start(129) + .origin("src/display.rs"), + ); let renderer = Renderer::styled(); - anstream::println!("{}", renderer.render(snippet)); + anstream::println!("{}", renderer.render(message)); } diff --git a/src/lib.rs b/src/lib.rs index 8602c0a..a2e4231 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,10 +16,10 @@ //! The crate uses a three stage process with two conversions between states: //! //! ```text -//! Snippet --> Renderer --> impl Display +//! Message --> Renderer --> impl Display //! ``` //! -//! The input type - [Snippet] is a structure designed +//! The input type - [Message] is a structure designed //! to align with likely output from any parser whose code snippet is to be //! annotated. //! diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 01484d4..d54282f 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -106,23 +106,30 @@ impl<'a> DisplayList<'a> { const WARNING_TXT: &'static str = "warning"; pub(crate) fn new( - snippet::Snippet { - title, + snippet::Message { + level, id, + title, footer, - slices, - }: snippet::Snippet<'a>, + snippets, + }: snippet::Message<'a>, stylesheet: &'a Stylesheet, anonymized_line_numbers: bool, margin: Option, ) -> DisplayList<'a> { let mut body = vec![]; - body.push(format_title(title, id)); + body.push(format_title( + snippet::Label { + level, + label: title, + }, + id, + )); - for (idx, slice) in slices.into_iter().enumerate() { + for (idx, snippet) in snippets.into_iter().enumerate() { body.append(&mut format_slice( - slice, + snippet, idx == 0, !footer.is_empty(), margin, @@ -542,7 +549,7 @@ pub enum DisplayLine<'a> { /// A source line. #[derive(Debug, PartialEq)] pub enum DisplaySourceLine<'a> { - /// A line with the content of the Slice. + /// A line with the content of the Snippet. Content { text: &'a str, range: (usize, usize), // meta information for annotation placement. @@ -649,14 +656,14 @@ pub enum DisplayAnnotationType { Help, } -impl From for DisplayAnnotationType { - fn from(at: snippet::AnnotationType) -> Self { +impl From for DisplayAnnotationType { + fn from(at: snippet::Level) -> Self { match at { - snippet::AnnotationType::Error => DisplayAnnotationType::Error, - snippet::AnnotationType::Warning => DisplayAnnotationType::Warning, - snippet::AnnotationType::Info => DisplayAnnotationType::Info, - snippet::AnnotationType::Note => DisplayAnnotationType::Note, - snippet::AnnotationType::Help => DisplayAnnotationType::Help, + snippet::Level::Error => DisplayAnnotationType::Error, + snippet::Level::Warning => DisplayAnnotationType::Warning, + snippet::Level::Info => DisplayAnnotationType::Info, + snippet::Level::Note => DisplayAnnotationType::Note, + snippet::Level::Help => DisplayAnnotationType::Help, } } } @@ -736,7 +743,7 @@ fn format_label( fn format_title<'a>(title: snippet::Label<'a>, id: Option<&'a str>) -> DisplayLine<'a> { DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { - annotation_type: DisplayAnnotationType::from(title.annotation_type), + annotation_type: DisplayAnnotationType::from(title.level), id, label: format_label(Some(title.label), Some(DisplayTextStyle::Emphasis)), }, @@ -750,7 +757,7 @@ fn format_footer(footer: snippet::Label<'_>) -> Vec> { for (i, line) in footer.label.lines().enumerate() { result.push(DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { - annotation_type: DisplayAnnotationType::from(footer.annotation_type), + annotation_type: DisplayAnnotationType::from(footer.level), id: None, label: format_label(Some(line), None), }, @@ -762,15 +769,15 @@ fn format_footer(footer: snippet::Label<'_>) -> Vec> { } fn format_slice( - slice: snippet::Slice<'_>, + snippet: snippet::Snippet<'_>, is_first: bool, has_footer: bool, margin: Option, ) -> Vec> { - let main_range = slice.annotations.first().map(|x| x.range.start); - let origin = slice.origin; + let main_range = snippet.annotations.first().map(|x| x.range.start); + let origin = snippet.origin; let need_empty_header = origin.is_some() || is_first; - let mut body = format_body(slice, need_empty_header, has_footer, margin); + let mut body = format_body(snippet, need_empty_header, has_footer, margin); let header = format_header(origin, main_range, &body, is_first); let mut result = vec![]; @@ -942,13 +949,13 @@ fn fold_body(mut body: Vec>) -> Vec> { } fn format_body( - slice: snippet::Slice<'_>, + snippet: snippet::Snippet<'_>, need_empty_header: bool, has_footer: bool, margin: Option, ) -> Vec> { - let source_len = slice.source.len(); - if let Some(bigger) = slice.annotations.iter().find_map(|x| { + let source_len = snippet.source.len(); + if let Some(bigger) = snippet.annotations.iter().find_map(|x| { // Allow highlighting one past the last character in the source. if source_len + 1 < x.range.end { Some(&x.range) @@ -963,7 +970,7 @@ fn format_body( } let mut body = vec![]; - let mut current_line = slice.line_start; + let mut current_line = snippet.line_start; let mut current_index = 0; let mut line_info = vec![]; @@ -972,7 +979,7 @@ fn format_body( line_end_index: usize, } - for (line, end_line) in CursorLines::new(slice.source) { + for (line, end_line) in CursorLines::new(snippet.source) { let line_length: usize = line .chars() .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) @@ -995,7 +1002,7 @@ fn format_body( } let mut annotation_line_count = 0; - let mut annotations = slice.annotations; + let mut annotations = snippet.annotations; for ( idx, LineInfo { @@ -1010,10 +1017,10 @@ fn format_body( // It would be nice to use filter_drain here once it's stable. annotations.retain(|annotation| { let body_idx = idx + annotation_line_count; - let annotation_type = match annotation.annotation_type { - snippet::AnnotationType::Error => DisplayAnnotationType::None, - snippet::AnnotationType::Warning => DisplayAnnotationType::None, - _ => DisplayAnnotationType::from(annotation.annotation_type), + let annotation_type = match annotation.level { + snippet::Level::Error => DisplayAnnotationType::None, + snippet::Level::Warning => DisplayAnnotationType::None, + _ => DisplayAnnotationType::from(annotation.level), }; match annotation.range { Range { start, .. } if start > line_end_index => true, @@ -1033,12 +1040,10 @@ fn format_body( annotation: Annotation { annotation_type, id: None, - label: format_label(Some(annotation.label), None), + label: format_label(annotation.label, None), }, range, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), + annotation_type: DisplayAnnotationType::from(annotation.level), annotation_part: DisplayAnnotationPart::Standalone, }, }, @@ -1059,9 +1064,7 @@ fn format_body( { inline_marks.push(DisplayMark { mark_type: DisplayMarkType::AnnotationStart, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), + annotation_type: DisplayAnnotationType::from(annotation.level), }); } } else { @@ -1079,9 +1082,7 @@ fn format_body( label: vec![], }, range, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), + annotation_type: DisplayAnnotationType::from(annotation.level), annotation_part: DisplayAnnotationPart::MultilineStart, }, }, @@ -1098,9 +1099,7 @@ fn format_body( { inline_marks.push(DisplayMark { mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), + annotation_type: DisplayAnnotationType::from(annotation.level), }); } true @@ -1117,9 +1116,7 @@ fn format_body( { inline_marks.push(DisplayMark { mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), + annotation_type: DisplayAnnotationType::from(annotation.level), }); } @@ -1131,20 +1128,16 @@ fn format_body( lineno: None, inline_marks: vec![DisplayMark { mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), + annotation_type: DisplayAnnotationType::from(annotation.level), }], line: DisplaySourceLine::Annotation { annotation: Annotation { annotation_type, id: None, - label: format_label(Some(annotation.label), None), + label: format_label(annotation.label, None), }, range, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), + annotation_type: DisplayAnnotationType::from(annotation.level), annotation_part: DisplayAnnotationPart::MultilineEnd, }, }, @@ -1157,7 +1150,7 @@ fn format_body( }); } - if slice.fold { + if snippet.fold { body = fold_body(body); } @@ -1220,7 +1213,7 @@ mod tests { #[test] fn test_format_title() { - let input = snippet::Snippet::error("This is a title").id("E0001"); + let input = snippet::Level::Error.title("This is a title").id("E0001"); let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Error, @@ -1241,7 +1234,9 @@ mod tests { let line_1 = "This is line 1"; let line_2 = "This is line 2"; let source = [line_1, line_2].join("\n"); - let input = snippet::Snippet::error("").slice(snippet::Slice::new(&source, 5402)); + let input = snippet::Level::Error + .title("") + .snippet(snippet::Snippet::source(&source).line_start(5402)); let output = from_display_lines(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { @@ -1291,9 +1286,18 @@ mod tests { let src_0_len = src_0.len(); let src_1 = "This is slice 2"; let src_1_len = src_1.len(); - let input = snippet::Snippet::error("") - .slice(snippet::Slice::new(src_0, 5402).origin("file1.rs")) - .slice(snippet::Slice::new(src_1, 2).origin("file2.rs")); + let input = snippet::Level::Error + .title("") + .snippet( + snippet::Snippet::source(src_0) + .line_start(5402) + .origin("file1.rs"), + ) + .snippet( + snippet::Snippet::source(src_1) + .line_start(2) + .origin("file2.rs"), + ); let output = from_display_lines(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { @@ -1364,9 +1368,14 @@ mod tests { let source = [line_1, line_2].join("\n"); // In line 2 let range = 22..24; - let input = snippet::Snippet::error("").slice( - snippet::Slice::new(&source, 5402) - .annotation(snippet::Label::info("Test annotation").span(range.clone())), + let input = snippet::Level::Error.title("").snippet( + snippet::Snippet::source(&source) + .line_start(5402) + .annotation( + snippet::Level::Info + .span(range.clone()) + .label("Test annotation"), + ), ); let output = from_display_lines(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { @@ -1433,8 +1442,9 @@ mod tests { #[test] fn test_format_label() { - let input = - snippet::Snippet::error("").footer(snippet::Label::error("This __is__ a title")); + let input = snippet::Level::Error + .title("") + .footer(snippet::Label::error("This __is__ a title")); let output = from_display_lines(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { @@ -1469,20 +1479,22 @@ mod tests { fn test_i26() { let source = "short"; let label = "label"; - let input = snippet::Snippet::error("").slice( - snippet::Slice::new(source, 0) - .annotation(snippet::Label::error(label).span(0..source.len() + 2)), + let input = snippet::Level::Error.title("").snippet( + snippet::Snippet::source(source) + .line_start(0) + .annotation(snippet::Level::Error.span(0..source.len() + 2).label(label)), ); let _ = DisplayList::new(input, &STYLESHEET, false, None); } #[test] fn test_i_29() { - let snippets = snippet::Snippet::error("oops").slice( - snippet::Slice::new("First line\r\nSecond oops line", 1) + let snippets = snippet::Level::Error.title("oops").snippet( + snippet::Snippet::source("First line\r\nSecond oops line") + .line_start(1) .origin("") .fold(true) - .annotation(snippet::Label::error("oops").span(19..23)), + .annotation(snippet::Level::Error.span(19..23).label("oops")), ); let expected = from_display_lines(vec![ diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 7046407..5f9394d 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,11 +1,11 @@ -//! The renderer for [`Snippet`]s +//! The renderer for [`Message`]s //! //! # Example //! ``` -//! use annotate_snippets::{Renderer, Slice, Snippet}; -//! let snippet = Snippet::error("mismatched types") -//! .slice(Slice::new("Foo", 51).origin("src/format.rs")) -//! .slice(Slice::new("Faa", 129).origin("src/display.rs")); +//! use annotate_snippets::{Renderer, Snippet, Level}; +//! let snippet = Level::Error.title("mismatched types") +//! .snippet(Snippet::source("Foo").line_start(51).origin("src/format.rs")) +//! .snippet(Snippet::source("Faa").line_start(129).origin("src/display.rs")); //! //! let renderer = Renderer::styled(); //! println!("{}", renderer.render(snippet)); @@ -14,14 +14,14 @@ mod display_list; mod margin; pub(crate) mod stylesheet; -use crate::snippet::Snippet; +use crate::snippet::Message; pub use anstyle::*; use display_list::DisplayList; pub use margin::Margin; use std::fmt::Display; use stylesheet::Stylesheet; -/// A renderer for [`Snippet`]s +/// A renderer for [`Message`]s #[derive(Clone)] pub struct Renderer { anonymized_line_numbers: bool, @@ -165,9 +165,9 @@ impl Renderer { } /// Render a snippet into a `Display`able object - pub fn render<'a>(&'a self, snippet: Snippet<'a>) -> impl Display + 'a { + pub fn render<'a>(&'a self, msg: Message<'a>) -> impl Display + 'a { DisplayList::new( - snippet, + msg, &self.stylesheet, self.anonymized_line_numbers, self.margin, diff --git a/src/snippet.rs b/src/snippet.rs index 37f431a..6ba2d2d 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -5,58 +5,37 @@ //! ``` //! use annotate_snippets::*; //! -//! Snippet::error("mismatched types") -//! .slice(Slice::new("Foo", 51).origin("src/format.rs")) -//! .slice(Slice::new("Faa", 129).origin("src/display.rs")); +//! Level::Error.title("mismatched types") +//! .snippet(Snippet::source("Foo").line_start(51).origin("src/format.rs")) +//! .snippet(Snippet::source("Faa").line_start(129).origin("src/display.rs")); //! ``` use std::ops::Range; /// Primary structure provided for formatting -pub struct Snippet<'a> { - pub(crate) title: Label<'a>, +/// +/// See [`Level::title`] to create a [`Message`] +pub struct Message<'a> { + pub(crate) level: Level, pub(crate) id: Option<&'a str>, - pub(crate) slices: Vec>, + pub(crate) title: &'a str, + pub(crate) snippets: Vec>, pub(crate) footer: Vec>, } -impl<'a> Snippet<'a> { - pub fn title(title: Label<'a>) -> Self { - Self { - title, - id: None, - slices: vec![], - footer: vec![], - } - } - - pub fn error(title: &'a str) -> Self { - Self::title(Label::error(title)) - } - - pub fn warning(title: &'a str) -> Self { - Self::title(Label::warning(title)) - } - - pub fn info(title: &'a str) -> Self { - Self::title(Label::info(title)) - } - - pub fn note(title: &'a str) -> Self { - Self::title(Label::note(title)) - } - - pub fn help(title: &'a str) -> Self { - Self::title(Label::help(title)) - } - +impl<'a> Message<'a> { pub fn id(mut self, id: &'a str) -> Self { self.id = Some(id); self } - pub fn slice(mut self, slice: Slice<'a>) -> Self { - self.slices.push(slice); + pub fn snippet(mut self, slice: Snippet<'a>) -> Self { + self.snippets.push(slice); + self + } + + pub fn snippets(mut self, slice: impl IntoIterator>) -> Self { + self.snippets.extend(slice); self } @@ -64,107 +43,122 @@ impl<'a> Snippet<'a> { self.footer.push(footer); self } + + pub fn footers(mut self, footer: impl IntoIterator>) -> Self { + self.footer.extend(footer); + self + } } pub struct Label<'a> { - pub(crate) annotation_type: AnnotationType, + pub(crate) level: Level, pub(crate) label: &'a str, } impl<'a> Label<'a> { - pub fn new(annotation_type: AnnotationType, label: &'a str) -> Self { - Self { - annotation_type, - label, - } + pub fn new(level: Level, label: &'a str) -> Self { + Self { level, label } } pub fn error(label: &'a str) -> Self { - Self::new(AnnotationType::Error, label) + Self::new(Level::Error, label) } pub fn warning(label: &'a str) -> Self { - Self::new(AnnotationType::Warning, label) + Self::new(Level::Warning, label) } pub fn info(label: &'a str) -> Self { - Self::new(AnnotationType::Info, label) + Self::new(Level::Info, label) } pub fn note(label: &'a str) -> Self { - Self::new(AnnotationType::Note, label) + Self::new(Level::Note, label) } pub fn help(label: &'a str) -> Self { - Self::new(AnnotationType::Help, label) + Self::new(Level::Help, label) } pub fn label(mut self, label: &'a str) -> Self { self.label = label; self } - - /// Create a [`SourceAnnotation`] with the given span for a [`Slice`] - pub fn span(&self, span: Range) -> SourceAnnotation<'a> { - SourceAnnotation { - range: span, - label: self.label, - annotation_type: self.annotation_type, - } - } -} - -impl From for Label<'_> { - fn from(annotation_type: AnnotationType) -> Self { - Label { - annotation_type, - label: "", - } - } } /// Structure containing the slice of text to be annotated and /// basic information about the location of the slice. /// -/// One `Slice` is meant to represent a single, continuous, +/// One `Snippet` is meant to represent a single, continuous, /// slice of source code that you want to annotate. -pub struct Slice<'a> { - pub(crate) source: &'a str, - pub(crate) line_start: usize, +pub struct Snippet<'a> { pub(crate) origin: Option<&'a str>, - pub(crate) annotations: Vec>, + pub(crate) line_start: usize, + + pub(crate) source: &'a str, + pub(crate) annotations: Vec>, + pub(crate) fold: bool, } -impl<'a> Slice<'a> { - pub fn new(source: &'a str, line_start: usize) -> Self { +impl<'a> Snippet<'a> { + pub fn source(source: &'a str) -> Self { Self { - source, - line_start, origin: None, + line_start: 1, + source, annotations: vec![], fold: false, } } + pub fn line_start(mut self, line_start: usize) -> Self { + self.line_start = line_start; + self + } + pub fn origin(mut self, origin: &'a str) -> Self { self.origin = Some(origin); self } - pub fn annotation(mut self, annotation: SourceAnnotation<'a>) -> Self { + pub fn annotation(mut self, annotation: Annotation<'a>) -> Self { self.annotations.push(annotation); self } + pub fn annotations(mut self, annotation: impl IntoIterator>) -> Self { + self.annotations.extend(annotation); + self + } + + /// Hide lines without [`Annotation`]s pub fn fold(mut self, fold: bool) -> Self { self.fold = fold; self } } +/// An annotation for a [`Snippet`]. +/// +/// See [`Level::span`] to create a [`Annotation`] +#[derive(Debug)] +pub struct Annotation<'a> { + /// The byte range of the annotation in the `source` string + pub(crate) range: Range, + pub(crate) label: Option<&'a str>, + pub(crate) level: Level, +} + +impl<'a> Annotation<'a> { + pub fn label(mut self, label: &'a str) -> Self { + self.label = Some(label); + self + } +} + /// Types of annotations. #[derive(Debug, Clone, Copy, PartialEq)] -pub enum AnnotationType { +pub enum Level { /// Error annotations are displayed using red color and "^" character. Error, /// Warning annotations are displayed using blue color and "-" character. @@ -174,13 +168,23 @@ pub enum AnnotationType { Help, } -/// An annotation for a [`Slice`]. -/// -/// This gets created by [`Label::span`]. -#[derive(Debug)] -pub struct SourceAnnotation<'a> { - /// The byte range of the annotation in the `source` string - pub(crate) range: Range, - pub(crate) label: &'a str, - pub(crate) annotation_type: AnnotationType, +impl Level { + pub fn title(self, title: &str) -> Message<'_> { + Message { + level: self, + id: None, + title, + snippets: vec![], + footer: vec![], + } + } + + /// Create a [`Annotation`] with the given span for a [`Snippet`] + pub fn span<'a>(self, span: Range) -> Annotation<'a> { + Annotation { + range: span, + label: None, + level: self, + } + } } diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index a01c343..6bfe76f 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -1,23 +1,22 @@ use serde::{Deserialize, Deserializer, Serialize}; use std::ops::Range; -use annotate_snippets::{ - renderer::Margin, AnnotationType, Label, Renderer, Slice, Snippet, SourceAnnotation, -}; +use annotate_snippets::{renderer::Margin, Annotation, Label, Level, Message, Renderer, Snippet}; #[derive(Deserialize)] pub struct Fixture<'a> { #[serde(default)] pub renderer: RendererDef, #[serde(borrow)] - pub snippet: SnippetDef<'a>, + pub message: MessageDef<'a>, } #[derive(Deserialize)] -pub struct SnippetDef<'a> { - #[serde(deserialize_with = "deserialize_label")] +pub struct MessageDef<'a> { + #[serde(with = "LevelDef")] + pub level: Level, #[serde(borrow)] - pub title: Label<'a>, + pub title: &'a str, #[serde(default)] #[serde(borrow)] pub id: Option<&'a str>, @@ -25,48 +24,30 @@ pub struct SnippetDef<'a> { #[serde(default)] #[serde(borrow)] pub footer: Vec>, - #[serde(deserialize_with = "deserialize_slices")] + #[serde(deserialize_with = "deserialize_snippets")] #[serde(borrow)] - pub slices: Vec>, + pub snippets: Vec>, } -impl<'a> From> for Snippet<'a> { - fn from(val: SnippetDef<'a>) -> Self { - let SnippetDef { +impl<'a> From> for Message<'a> { + fn from(val: MessageDef<'a>) -> Self { + let MessageDef { + level, title, id, footer, - slices, + snippets, } = val; - let mut snippet = Snippet::title(title); + let mut message = level.title(title); if let Some(id) = id { - snippet = snippet.id(id); + message = message.id(id); } - snippet = slices - .into_iter() - .fold(snippet, |snippet, slice| snippet.slice(slice)); - snippet = footer - .into_iter() - .fold(snippet, |snippet, label| snippet.footer(label)); - snippet + message = message.snippets(snippets); + message = message.footers(footer); + message } } -fn deserialize_label<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - struct Wrapper<'a>( - #[serde(with = "LabelDef")] - #[serde(borrow)] - LabelDef<'a>, - ); - - Wrapper::deserialize(deserializer) - .map(|Wrapper(label)| Label::new(label.annotation_type, label.label)) -} - fn deserialize_labels<'de, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, @@ -80,19 +61,19 @@ where let v = Vec::deserialize(deserializer)?; Ok(v.into_iter() - .map(|Wrapper(a)| Label::new(a.annotation_type, a.label)) + .map(|Wrapper(a)| Label::new(a.level, a.label)) .collect()) } -fn deserialize_slices<'de, D>(deserializer: D) -> Result>, D::Error> +fn deserialize_snippets<'de, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, { #[derive(Deserialize)] struct Wrapper<'a>( - #[serde(with = "SliceDef")] + #[serde(with = "SnippetDef")] #[serde(borrow)] - SliceDef<'a>, + SnippetDef<'a>, ); let v = Vec::deserialize(deserializer)?; @@ -100,84 +81,80 @@ where } #[derive(Deserialize)] -pub struct SliceDef<'a> { +pub struct SnippetDef<'a> { #[serde(borrow)] pub source: &'a str, pub line_start: usize, #[serde(borrow)] pub origin: Option<&'a str>, - #[serde(deserialize_with = "deserialize_source_annotations")] + #[serde(deserialize_with = "deserialize_annotations")] #[serde(borrow)] - pub annotations: Vec>, + pub annotations: Vec>, #[serde(default)] pub fold: bool, } -impl<'a> From> for Slice<'a> { - fn from(val: SliceDef<'a>) -> Self { - let SliceDef { +impl<'a> From> for Snippet<'a> { + fn from(val: SnippetDef<'a>) -> Self { + let SnippetDef { source, line_start, origin, annotations, fold, } = val; - let mut slice = Slice::new(source, line_start).fold(fold); + let mut snippet = Snippet::source(source).line_start(line_start).fold(fold); if let Some(origin) = origin { - slice = slice.origin(origin) + snippet = snippet.origin(origin) } - slice = annotations - .into_iter() - .fold(slice, |slice, annotation| slice.annotation(annotation)); - slice + snippet = snippet.annotations(annotations); + snippet } } -fn deserialize_source_annotations<'de, D>( - deserializer: D, -) -> Result>, D::Error> +fn deserialize_annotations<'de, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, { #[derive(Deserialize)] - struct Wrapper<'a>(#[serde(borrow)] SourceAnnotationDef<'a>); + struct Wrapper<'a>(#[serde(borrow)] AnnotationDef<'a>); let v = Vec::deserialize(deserializer)?; Ok(v.into_iter().map(|Wrapper(a)| a.into()).collect()) } #[derive(Serialize, Deserialize)] -pub struct SourceAnnotationDef<'a> { +pub struct AnnotationDef<'a> { pub range: Range, #[serde(borrow)] pub label: &'a str, - #[serde(with = "AnnotationTypeDef")] - pub annotation_type: AnnotationType, + #[serde(with = "LevelDef")] + pub level: Level, } -impl<'a> From> for SourceAnnotation<'a> { - fn from(val: SourceAnnotationDef<'a>) -> Self { - let SourceAnnotationDef { +impl<'a> From> for Annotation<'a> { + fn from(val: AnnotationDef<'a>) -> Self { + let AnnotationDef { range, label, - annotation_type, + level, } = val; - Label::new(annotation_type, label).span(range) + level.span(range).label(label) } } #[derive(Serialize, Deserialize)] pub struct LabelDef<'a> { - #[serde(with = "AnnotationTypeDef")] - pub annotation_type: AnnotationType, + #[serde(with = "LevelDef")] + pub level: Level, #[serde(borrow)] pub label: &'a str, } #[allow(dead_code)] #[derive(Serialize, Deserialize)] -#[serde(remote = "AnnotationType")] -enum AnnotationTypeDef { +#[serde(remote = "Level")] +enum LevelDef { Error, Warning, Info, diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs index c320407..841b363 100644 --- a/tests/fixtures/main.rs +++ b/tests/fixtures/main.rs @@ -1,7 +1,7 @@ mod deserialize; use crate::deserialize::Fixture; -use annotate_snippets::{Renderer, Snippet}; +use annotate_snippets::{Message, Renderer}; use snapbox::data::DataFormat; use snapbox::Data; use std::error::Error; @@ -26,8 +26,8 @@ fn setup(input_path: std::path::PathBuf) -> snapbox::harness::Case { fn test(input_path: &std::path::Path) -> Result> { let src = std::fs::read_to_string(input_path)?; - let (renderer, snippet): (Renderer, Snippet<'_>) = - toml::from_str(&src).map(|a: Fixture| (a.renderer.into(), a.snippet.into()))?; - let actual = renderer.render(snippet).to_string(); + let (renderer, message): (Renderer, Message<'_>) = + toml::from_str(&src).map(|a: Fixture| (a.renderer.into(), a.message.into()))?; + let actual = renderer.render(message).to_string(); Ok(Data::from(actual).coerce_to(DataFormat::TermSvg)) } diff --git a/tests/fixtures/no-color/issue_52.toml b/tests/fixtures/no-color/issue_52.toml index ea239dd..1e81a71 100644 --- a/tests/fixtures/no-color/issue_52.toml +++ b/tests/fixtures/no-color/issue_52.toml @@ -1,8 +1,8 @@ -[snippet.title] -annotation_type = "Error" -label = "" +[message] +level = "Error" +title = "" -[[snippet.slices]] +[[message.snippets]] source = """ @@ -11,7 +11,7 @@ invalid syntax line_start = 1 origin = "path/to/error.rs" fold = true -[[snippet.slices.annotations]] +[[message.snippets.annotations]] label = "error here" -annotation_type = "Warning" +level = "Warning" range = [2,16] diff --git a/tests/fixtures/no-color/issue_9.toml b/tests/fixtures/no-color/issue_9.toml index ee1fe27..1f35243 100644 --- a/tests/fixtures/no-color/issue_9.toml +++ b/tests/fixtures/no-color/issue_9.toml @@ -1,28 +1,28 @@ -[snippet.title] -label = "expected one of `.`, `;`, `?`, or an operator, found `for`" -annotation_type = "Error" +[message] +level = "Error" +title = "expected one of `.`, `;`, `?`, or an operator, found `for`" -[[snippet.slices]] +[[message.snippets]] source = "let x = vec![1];" line_start = 4 origin = "/code/rust/src/test/ui/annotate-snippet/suggestion.rs" -[[snippet.slices.annotations]] +[[message.snippets.annotations]] label = "move occurs because `x` has type `std::vec::Vec`, which does not implement the `Copy` trait" -annotation_type = "Warning" +level = "Warning" range = [4, 5] -[[snippet.slices]] +[[message.snippets]] source = "let y = x;" line_start = 7 -[[snippet.slices.annotations]] +[[message.snippets.annotations]] label = "value moved here" -annotation_type = "Warning" +level = "Warning" range = [8, 9] -[[snippet.slices]] +[[message.snippets]] source = "x;" line_start = 9 -[[snippet.slices.annotations]] +[[message.snippets.annotations]] label = "value used here after move" -annotation_type = "Error" +level = "Error" range = [0, 1] diff --git a/tests/fixtures/no-color/multiline_annotation.toml b/tests/fixtures/no-color/multiline_annotation.toml index 48c0725..09fc7d4 100644 --- a/tests/fixtures/no-color/multiline_annotation.toml +++ b/tests/fixtures/no-color/multiline_annotation.toml @@ -1,4 +1,9 @@ -[[snippet.slices]] +[message] +level = "Error" +id = "E0308" +title = "mismatched types" + +[[message.snippets]] source = """ ) -> Option { for ann in annotations { @@ -26,15 +31,11 @@ source = """ line_start = 51 origin = "src/format.rs" fold = true -[[snippet.slices.annotations]] +[[message.snippets.annotations]] label = "expected `std::option::Option` because of return type" -annotation_type = "Warning" +level = "Warning" range = [5, 19] -[[snippet.slices.annotations]] +[[message.snippets.annotations]] label = "expected enum `std::option::Option`, found ()" -annotation_type = "Error" +level = "Error" range = [22, 766] - -[snippet] -title = { annotation_type = "Error", label = "mismatched types" } -id = "E0308" diff --git a/tests/fixtures/no-color/multiline_annotation2.toml b/tests/fixtures/no-color/multiline_annotation2.toml index 89294bc..671b534 100644 --- a/tests/fixtures/no-color/multiline_annotation2.toml +++ b/tests/fixtures/no-color/multiline_annotation2.toml @@ -1,4 +1,9 @@ -[[snippet.slices]] +[message] +level = "Error" +id = "E0027" +title = "pattern does not mention fields `lineno`, `content`" + +[[message.snippets]] source = """ if let DisplayLine::Source { ref mut inline_marks, @@ -7,11 +12,7 @@ source = """ line_start = 139 origin = "src/display_list.rs" fold = false -[[snippet.slices.annotations]] +[[message.snippets.annotations]] label = "missing fields `lineno`, `content`" -annotation_type = "Error" +level = "Error" range = [31, 128] - -[snippet] -title = { annotation_type = "Error", label = "pattern does not mention fields `lineno`, `content`" } -id = "E0027" diff --git a/tests/fixtures/no-color/multiline_annotation3.toml b/tests/fixtures/no-color/multiline_annotation3.toml index efc1f5f..dd85332 100644 --- a/tests/fixtures/no-color/multiline_annotation3.toml +++ b/tests/fixtures/no-color/multiline_annotation3.toml @@ -1,4 +1,9 @@ -[[snippet.slices]] +[message] +level = "Error" +id = "E####" +title = "spacing error found" + +[[message.snippets]] source = """ This is an exampl e of an edge case of an annotation overflowing @@ -7,11 +12,7 @@ to exactly one character on next line. line_start = 26 origin = "foo.txt" fold = false -[[snippet.slices.annotations]] +[[message.snippets.annotations]] label = "this should not be on separate lines" -annotation_type = "Error" +level = "Error" range = [11, 18] - -[snippet] -title = { annotation_type = "Error", label = "spacing error found" } -id = "E####" diff --git a/tests/fixtures/no-color/multiple_annotations.toml b/tests/fixtures/no-color/multiple_annotations.toml index ae35cef..842b137 100644 --- a/tests/fixtures/no-color/multiple_annotations.toml +++ b/tests/fixtures/no-color/multiple_annotations.toml @@ -1,8 +1,8 @@ -[snippet.title] -annotation_type = "Error" -label = "" +[message] +level = "Error" +title = "" -[[snippet.slices]] +[[message.snippets]] source = """ fn add_title_line(result: &mut Vec, main_annotation: Option<&Annotation>) { if let Some(annotation) = main_annotation { @@ -15,15 +15,15 @@ fn add_title_line(result: &mut Vec, main_annotation: Option<&Annotation> } """ line_start = 96 -[[snippet.slices.annotations]] +[[message.snippets.annotations]] label = "Variable defined here" -annotation_type = "Error" +level = "Error" range = [100, 110] -[[snippet.slices.annotations]] +[[message.snippets.annotations]] label = "Referenced here" -annotation_type = "Error" +level = "Error" range = [184, 194] -[[snippet.slices.annotations]] +[[message.snippets.annotations]] label = "Referenced again here" -annotation_type = "Error" +level = "Error" range = [243, 253] diff --git a/tests/fixtures/no-color/one_past.toml b/tests/fixtures/no-color/one_past.toml index 62d1d42..b681c29 100644 --- a/tests/fixtures/no-color/one_past.toml +++ b/tests/fixtures/no-color/one_past.toml @@ -1,12 +1,12 @@ -[snippet.title] -label = "expected `.`, `=`" -annotation_type = "Error" +[message] +level = "Error" +title = "expected `.`, `=`" -[[snippet.slices]] +[[message.snippets]] source = "asdf" line_start = 1 origin = "Cargo.toml" -[[snippet.slices.annotations]] +[[message.snippets.annotations]] label = "" -annotation_type = "Error" +level = "Error" range = [4, 5] diff --git a/tests/fixtures/no-color/simple.toml b/tests/fixtures/no-color/simple.toml index 2e9b6d8..76b5bac 100644 --- a/tests/fixtures/no-color/simple.toml +++ b/tests/fixtures/no-color/simple.toml @@ -1,18 +1,19 @@ -[[snippet.slices]] +[message] +level = "Error" +title = "expected one of `.`, `;`, `?`, or an operator, found `for`" + +[[message.snippets]] source = """ }) for line in &self.body {""" line_start = 169 origin = "src/format_color.rs" -[[snippet.slices.annotations]] +[[message.snippets.annotations]] label = "unexpected token" -annotation_type = "Error" +level = "Error" range = [20, 23] -[[snippet.slices.annotations]] +[[message.snippets.annotations]] label = "expected one of `.`, `;`, `?`, or an operator here" -annotation_type = "Warning" +level = "Warning" range = [10, 11] -[snippet.title] -label = "expected one of `.`, `;`, `?`, or an operator, found `for`" -annotation_type = "Error" \ No newline at end of file diff --git a/tests/fixtures/no-color/strip_line.toml b/tests/fixtures/no-color/strip_line.toml index 9de50aa..d44024b 100644 --- a/tests/fixtures/no-color/strip_line.toml +++ b/tests/fixtures/no-color/strip_line.toml @@ -1,15 +1,16 @@ -[snippet] -title = { annotation_type = "Error", label = "mismatched types" } +[message] +level = "Error" id = "E0308" +title = "mismatched types" -[[snippet.slices]] +[[message.snippets]] source = " let _: () = 42;" line_start = 4 origin = "$DIR/whitespace-trimming.rs" -[[snippet.slices.annotations]] +[[message.snippets.annotations]] label = "expected (), found integer" -annotation_type = "Error" +level = "Error" range = [192, 194] [renderer] diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/no-color/strip_line_char.toml index 72df2ab..e3f7482 100644 --- a/tests/fixtures/no-color/strip_line_char.toml +++ b/tests/fixtures/no-color/strip_line_char.toml @@ -1,15 +1,16 @@ -[snippet] -title = { annotation_type = "Error", label = "mismatched types" } +[message] +level = "Error" id = "E0308" +title = "mismatched types" -[[snippet.slices]] +[[message.snippets]] source = " let _: () = 42ñ" line_start = 4 origin = "$DIR/whitespace-trimming.rs" -[[snippet.slices.annotations]] +[[message.snippets.annotations]] label = "expected (), found integer" -annotation_type = "Error" +level = "Error" range = [192, 194] [renderer] diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/no-color/strip_line_non_ws.toml index 236d8f1..2985177 100644 --- a/tests/fixtures/no-color/strip_line_non_ws.toml +++ b/tests/fixtures/no-color/strip_line_non_ws.toml @@ -1,15 +1,16 @@ -[snippet] -title = { annotation_type = "Error", label = "mismatched types" } +[message] +level = "Error" id = "E0308" +title = "mismatched types" -[[snippet.slices]] +[[message.snippets]] source = " let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();" line_start = 4 origin = "$DIR/non-whitespace-trimming.rs" -[[snippet.slices.annotations]] +[[message.snippets.annotations]] label = "expected (), found integer" -annotation_type = "Error" +level = "Error" range = [240, 242] [renderer] diff --git a/tests/formatter.rs b/tests/formatter.rs index 8f5c09f..76dcb27 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1,11 +1,11 @@ -use annotate_snippets::{Label, Renderer, Slice, Snippet}; +use annotate_snippets::{Level, Renderer, Snippet}; #[test] fn test_i_29() { - let snippets = Snippet::error("oops").slice( - Slice::new("First line\r\nSecond oops line", 1) + let snippets = Level::Error.title("oops").snippet( + Snippet::source("First line\r\nSecond oops line") .origin("") - .annotation(Label::error("oops").span(19..23)) + .annotation(Level::Error.span(19..23).label("oops")) .fold(true), ); let expected = r#"error: oops @@ -22,10 +22,10 @@ fn test_i_29() { #[test] fn test_point_to_double_width_characters() { - let snippets = Snippet::error("").slice( - Slice::new("こんにちは、世界", 1) + let snippets = Level::Error.title("").snippet( + Snippet::source("こんにちは、世界") .origin("") - .annotation(Label::error("world").span(12..16)), + .annotation(Level::Error.span(12..16).label("world")), ); let expected = r#"error @@ -41,10 +41,10 @@ fn test_point_to_double_width_characters() { #[test] fn test_point_to_double_width_characters_across_lines() { - let snippets = Snippet::error("").slice( - Slice::new("おはよう\nございます", 1) + let snippets = Level::Error.title("").snippet( + Snippet::source("おはよう\nございます") .origin("") - .annotation(Label::error("Good morning").span(4..15)), + .annotation(Level::Error.span(4..15).label("Good morning")), ); let expected = r#"error @@ -62,11 +62,11 @@ fn test_point_to_double_width_characters_across_lines() { #[test] fn test_point_to_double_width_characters_multiple() { - let snippets = Snippet::error("").slice( - Slice::new("お寿司\n食べたい🍣", 1) + let snippets = Level::Error.title("").snippet( + Snippet::source("お寿司\n食べたい🍣") .origin("") - .annotation(Label::error("Sushi1").span(0..6)) - .annotation(Label::note("Sushi2").span(11..15)), + .annotation(Level::Error.span(0..6).label("Sushi1")) + .annotation(Level::Note.span(11..15).label("Sushi2")), ); let expected = r#"error @@ -84,10 +84,10 @@ fn test_point_to_double_width_characters_multiple() { #[test] fn test_point_to_double_width_characters_mixed() { - let snippets = Snippet::error("").slice( - Slice::new("こんにちは、新しいWorld!", 1) + let snippets = Level::Error.title("").snippet( + Snippet::source("こんにちは、新しいWorld!") .origin("") - .annotation(Label::error("New world").span(12..23)), + .annotation(Level::Error.span(12..23).label("New world")), ); let expected = r#"error