diff --git a/Cargo.toml b/Cargo.toml index 086db9d..d205c63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ yansi-term = { version = "0.1", optional = true } [dev-dependencies] glob = "0.3" -serde_yaml = "0.8" +toml = "0.5" serde = { version = "1.0", features = ["derive"] } difference = "2.0" yansi-term = "0.1" diff --git a/benches/simple.rs b/benches/simple.rs index a0da903..4c13a8f 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -33,27 +33,26 @@ fn create_snippet() { } _ => continue, } - }"# - .to_string(), + }"#, line_start: 51, - origin: Some("src/format.rs".to_string()), + origin: Some("src/format.rs"), fold: false, annotations: vec![ SourceAnnotation { - label: "expected `Option` because of return type".to_string(), + label: "expected `Option` because of return type", annotation_type: AnnotationType::Warning, range: (5, 19), }, SourceAnnotation { - label: "expected enum `std::option::Option`".to_string(), + label: "expected enum `std::option::Option`", annotation_type: AnnotationType::Error, range: (26, 724), }, ], }], title: Some(Annotation { - label: Some("mismatched types".to_string()), - id: Some("E0308".to_string()), + label: Some("mismatched types"), + id: Some("E0308"), annotation_type: AnnotationType::Error, }), footer: vec![], diff --git a/examples/expected_type.rs b/examples/expected_type.rs index b12ebad..89f0ddc 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -6,7 +6,7 @@ use annotate_snippets::{ fn main() { let snippet = Snippet { title: Some(Annotation { - label: Some("expected type, found `22`".to_string()), + label: Some("expected type, found `22`"), id: None, annotation_type: AnnotationType::Error, }), @@ -14,20 +14,19 @@ fn main() { slices: vec![Slice { source: r#" annotations: vec![SourceAnnotation { label: "expected struct `annotate_snippets::snippet::Slice`, found reference" - .to_string(), - range: <22, 25>,"# - .to_string(), + , + range: <22, 25>,"#, line_start: 26, - origin: Some("examples/footer.rs".to_string()), + origin: Some("examples/footer.rs"), fold: true, annotations: vec![ SourceAnnotation { - label: "".to_string(), + label: "", annotation_type: AnnotationType::Error, range: (205, 207), }, SourceAnnotation { - label: "while parsing this struct".to_string(), + label: "while parsing this struct", annotation_type: AnnotationType::Info, range: (34, 50), }, diff --git a/examples/footer.rs b/examples/footer.rs index 4e331d3..f3c15c4 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -6,26 +6,24 @@ use annotate_snippets::{ fn main() { let snippet = Snippet { title: Some(Annotation { - label: Some("mismatched types".to_string()), - id: Some("E0308".to_string()), + label: Some("mismatched types"), + id: Some("E0308"), annotation_type: AnnotationType::Error, }), footer: vec![Annotation { label: Some( - "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`" - .to_string(), + "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`", ), id: None, annotation_type: AnnotationType::Note, }], slices: vec![Slice { - source: " slices: vec![\"A\",".to_string(), + source: " slices: vec![\"A\",", line_start: 13, - origin: Some("src/multislice.rs".to_string()), + origin: Some("src/multislice.rs"), fold: false, annotations: vec![SourceAnnotation { - label: "expected struct `annotate_snippets::snippet::Slice`, found reference" - .to_string(), + label: "expected struct `annotate_snippets::snippet::Slice`, found reference", range: (21, 24), annotation_type: AnnotationType::Error, }], diff --git a/examples/format.rs b/examples/format.rs index d8c73ee..98b77a1 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -27,27 +27,26 @@ fn main() { } _ => continue, } - }"# - .to_string(), + }"#, line_start: 51, - origin: Some("src/format.rs".to_string()), + origin: Some("src/format.rs"), fold: false, annotations: vec![ SourceAnnotation { - label: "expected `Option` because of return type".to_string(), + label: "expected `Option` because of return type", annotation_type: AnnotationType::Warning, range: (5, 19), }, SourceAnnotation { - label: "expected enum `std::option::Option`".to_string(), + label: "expected enum `std::option::Option`", annotation_type: AnnotationType::Error, range: (26, 724), }, ], }], title: Some(Annotation { - label: Some("mismatched types".to_string()), - id: Some("E0308".to_string()), + label: Some("mismatched types"), + id: Some("E0308"), annotation_type: AnnotationType::Error, }), footer: vec![], diff --git a/examples/multislice.rs b/examples/multislice.rs index fe71977..5675a07 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -6,23 +6,23 @@ use annotate_snippets::{ fn main() { let snippet = Snippet { title: Some(Annotation { - label: Some("mismatched types".to_string()), + label: Some("mismatched types"), id: None, annotation_type: AnnotationType::Error, }), footer: vec![], slices: vec![ Slice { - source: "Foo".to_string(), + source: "Foo", line_start: 51, - origin: Some("src/format.rs".to_string()), + origin: Some("src/format.rs"), fold: false, annotations: vec![], }, Slice { - source: "Faa".to_string(), + source: "Faa", line_start: 129, - origin: Some("src/display.rs".to_string()), + origin: Some("src/display.rs"), fold: false, annotations: vec![], }, diff --git a/src/display_list/from_snippet.rs b/src/display_list/from_snippet.rs index d9e1dd3..b3ba5a3 100644 --- a/src/display_list/from_snippet.rs +++ b/src/display_list/from_snippet.rs @@ -2,7 +2,55 @@ use super::*; use crate::{formatter::get_term_style, snippet}; -fn format_label(label: Option<&str>, style: Option) -> Vec { +struct CursorLines<'a>(&'a str); + +impl<'a> CursorLines<'a> { + fn new(src: &str) -> CursorLines<'_> { + CursorLines(src) + } +} + +enum EndLine { + EOF = 0, + CRLF = 1, + LF = 2, +} + +impl<'a> Iterator for CursorLines<'a> { + type Item = (&'a str, EndLine); + + fn next(&mut self) -> Option { + if self.0.is_empty() { + None + } else { + self.0 + .find('\n') + .map(|x| { + let ret = if 0 < x { + if self.0.as_bytes()[x - 1] == b'\r' { + (&self.0[..x - 1], EndLine::LF) + } else { + (&self.0[..x], EndLine::CRLF) + } + } else { + ("", EndLine::CRLF) + }; + self.0 = &self.0[x + 1..]; + ret + }) + .or_else(|| { + let ret = Some((&self.0[..], EndLine::EOF)); + self.0 = ""; + ret + }) + } + } +} + +fn format_label( + label: Option<&str>, + style: Option, +) -> Vec> { let mut result = vec![]; if let Some(label) = label { for (idx, element) in label.split("__").enumerate() { @@ -17,7 +65,7 @@ fn format_label(label: Option<&str>, style: Option) -> Vec, style: Option) -> Vec DisplayLine { +fn format_title(annotation: snippet::Annotation<'_>) -> DisplayLine<'_> { let label = annotation.label.unwrap_or_default(); DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { @@ -38,7 +86,7 @@ fn format_title(annotation: snippet::Annotation) -> DisplayLine { }) } -fn format_annotation(annotation: snippet::Annotation) -> Vec { +fn format_annotation(annotation: snippet::Annotation<'_>) -> Vec> { let mut result = vec![]; let label = annotation.label.unwrap_or_default(); for (i, line) in label.lines().enumerate() { @@ -55,7 +103,11 @@ fn format_annotation(annotation: snippet::Annotation) -> Vec { result } -fn format_slice(mut slice: snippet::Slice, is_first: bool, has_footer: bool) -> Vec { +fn format_slice( + mut slice: snippet::Slice<'_>, + is_first: bool, + has_footer: bool, +) -> Vec> { let main_range = slice.annotations.get(0).map(|x| x.range.0); let row = slice.line_start; let origin = slice.origin.take(); @@ -70,13 +122,13 @@ fn format_slice(mut slice: snippet::Slice, is_first: bool, has_footer: bool) -> result } -fn format_header( - origin: Option, +fn format_header<'a>( + origin: Option<&'a str>, main_range: Option, mut row: usize, - body: &[DisplayLine], + body: &[DisplayLine<'_>], is_first: bool, -) -> Option { +) -> Option> { let display_header = if is_first { DisplayHeaderType::Initial } else { @@ -117,17 +169,19 @@ fn format_header( None } -fn fold_body(body: &[DisplayLine]) -> Vec { - let mut new_body = vec![]; +fn fold_body(mut body: Vec>) -> Vec> { + enum Line { + Fold(usize), + Source(usize), + }; + let mut lines = vec![]; let mut no_annotation_lines_counter = 0; - let mut idx = 0; - while idx < body.len() { - match body[idx] { + for (idx, line) in body.iter().enumerate() { + match line { DisplayLine::Source { line: DisplaySourceLine::Annotation { .. }, - ref inline_marks, .. } => { if no_annotation_lines_counter > 2 { @@ -143,40 +197,71 @@ fn fold_body(body: &[DisplayLine]) -> Vec { } else { 1 }; - for item in body.iter().take(fold_start + pre_len).skip(fold_start) { - new_body.push(item.clone()); + for (i, _) in body + .iter() + .enumerate() + .take(fold_start + pre_len) + .skip(fold_start) + { + lines.push(Line::Source(i)); } - new_body.push(DisplayLine::Fold { - inline_marks: inline_marks.clone(), - }); - for item in body.iter().take(fold_end).skip(fold_end - post_len) { - new_body.push(item.clone()); + lines.push(Line::Fold(idx)); + for (i, _) in body + .iter() + .enumerate() + .take(fold_end) + .skip(fold_end - post_len) + { + lines.push(Line::Source(i)); } } else { let start = idx - no_annotation_lines_counter; - for item in body.iter().take(idx).skip(start) { - new_body.push(item.clone()); + for (i, _) in body.iter().enumerate().take(idx).skip(start) { + lines.push(Line::Source(i)); } } no_annotation_lines_counter = 0; } DisplayLine::Source { .. } => { no_annotation_lines_counter += 1; - idx += 1; continue; } _ => { no_annotation_lines_counter += 1; } } - new_body.push(body[idx].clone()); - idx += 1; + lines.push(Line::Source(idx)); + } + + let mut new_body = vec![]; + let mut removed = 0; + for line in lines { + match line { + Line::Source(i) => { + new_body.push(body.remove(i - removed)); + removed += 1; + } + Line::Fold(i) => { + if let DisplayLine::Source { + line: DisplaySourceLine::Annotation { .. }, + ref inline_marks, + .. + } = body.get(i - removed).unwrap() + { + new_body.push(DisplayLine::Fold { + inline_marks: inline_marks.clone(), + }) + } else { + unreachable!() + } + } + } } new_body } -fn format_body(slice: snippet::Slice, has_footer: bool) -> Vec { +fn format_body(slice: snippet::Slice<'_>, has_footer: bool) -> Vec> { let source_len = slice.source.chars().count(); if let Some(bigger) = slice.annotations.iter().find_map(|x| { if source_len < x.range.1 { @@ -196,24 +281,20 @@ fn format_body(slice: snippet::Slice, has_footer: bool) -> Vec { let mut current_index = 0; let mut line_index_ranges = vec![]; - let lines = slice.source.lines(); - let lines_len = lines.clone().count(); - for (i, line) in lines.enumerate() { + for (line, end_line) in CursorLines::new(slice.source) { let line_length = line.chars().count(); let line_range = (current_index, current_index + line_length); body.push(DisplayLine::Source { lineno: Some(current_line), inline_marks: vec![], line: DisplaySourceLine::Content { - text: line.to_string(), + text: line, range: line_range, }, }); line_index_ranges.push(line_range); current_line += 1; - if i + 1 < lines_len { - current_index += line_length + 1; - } + current_index += line_length + end_line as usize; } let mut annotation_line_count = 0; @@ -361,7 +442,7 @@ fn format_body(slice: snippet::Slice, has_footer: bool) -> Vec { } if slice.fold { - body = fold_body(&body); + body = fold_body(body); } body.insert( @@ -388,16 +469,15 @@ fn format_body(slice: snippet::Slice, has_footer: bool) -> Vec { body } -// TODO: From reference to DisplayList<'a> -impl From for DisplayList { +impl<'a> From> for DisplayList<'a> { fn from( snippet::Snippet { title, footer, slices, opt, - }: snippet::Snippet, - ) -> Self { + }: snippet::Snippet<'a>, + ) -> DisplayList<'a> { let mut body = vec![]; if let Some(annotation) = title { body.push(format_title(annotation)); diff --git a/src/display_list/structs.rs b/src/display_list/structs.rs index 07c52ae..8f6d8fc 100644 --- a/src/display_list/structs.rs +++ b/src/display_list/structs.rs @@ -3,14 +3,14 @@ use std::fmt; use crate::formatter::{get_term_style, style::Stylesheet}; /// List of lines to be displayed. -pub struct DisplayList { - pub body: Vec, +pub struct DisplayList<'a> { + pub body: Vec>, pub stylesheet: Box, pub anonymized_line_numbers: bool, } -impl From> for DisplayList { - fn from(body: Vec) -> Self { +impl<'a> From>> for DisplayList<'a> { + fn from(body: Vec>) -> DisplayList<'a> { Self { body, anonymized_line_numbers: false, @@ -19,13 +19,13 @@ impl From> for DisplayList { } } -impl PartialEq for DisplayList { +impl<'a> PartialEq for DisplayList<'a> { fn eq(&self, other: &Self) -> bool { self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers } } -impl fmt::Debug for DisplayList { +impl<'a> fmt::Debug for DisplayList<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("DisplayList") .field("body", &self.body) @@ -41,42 +41,42 @@ pub struct FormatOptions { } /// Inline annotation which can be used in either Raw or Source line. -#[derive(Debug, Clone, PartialEq)] -pub struct Annotation { +#[derive(Debug, PartialEq)] +pub struct Annotation<'a> { pub annotation_type: DisplayAnnotationType, - pub id: Option, - pub label: Vec, + pub id: Option<&'a str>, + pub label: Vec>, } /// A single line used in `DisplayList`. -#[derive(Debug, Clone, PartialEq)] -pub enum DisplayLine { +#[derive(Debug, PartialEq)] +pub enum DisplayLine<'a> { /// A line with `lineno` portion of the slice. Source { lineno: Option, inline_marks: Vec, - line: DisplaySourceLine, + line: DisplaySourceLine<'a>, }, /// A line indicating a folded part of the slice. Fold { inline_marks: Vec }, /// A line which is displayed outside of slices. - Raw(DisplayRawLine), + Raw(DisplayRawLine<'a>), } /// A source line. -#[derive(Debug, Clone, PartialEq)] -pub enum DisplaySourceLine { +#[derive(Debug, PartialEq)] +pub enum DisplaySourceLine<'a> { /// A line with the content of the Slice. Content { - text: String, + text: &'a str, range: (usize, usize), // meta information for annotation placement. }, /// An annotation line which is displayed in context of the slice. Annotation { - annotation: Annotation, + annotation: Annotation<'a>, range: (usize, usize), annotation_type: DisplayAnnotationType, annotation_part: DisplayAnnotationPart, @@ -88,19 +88,19 @@ pub enum DisplaySourceLine { /// Raw line - a line which does not have the `lineno` part and is not considered /// a part of the snippet. -#[derive(Debug, Clone, PartialEq)] -pub enum DisplayRawLine { +#[derive(Debug, PartialEq)] +pub enum DisplayRawLine<'a> { /// A line which provides information about the location of the given /// slice in the project structure. Origin { - path: String, + path: &'a str, pos: Option<(usize, usize)>, header_type: DisplayHeaderType, }, /// An annotation line which is not part of any snippet. Annotation { - annotation: Annotation, + annotation: Annotation<'a>, /// If set to `true`, the annotation will be aligned to the /// lineno delimiter of the snippet. @@ -114,9 +114,9 @@ pub enum DisplayRawLine { } /// An inline text fragment which any label is composed of. -#[derive(Debug, Clone, PartialEq)] -pub struct DisplayTextFragment { - pub content: String, +#[derive(Debug, PartialEq)] +pub struct DisplayTextFragment<'a> { + pub content: &'a str, pub style: DisplayTextStyle, } diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs index f90d583..bf698a3 100644 --- a/src/formatter/mod.rs +++ b/src/formatter/mod.rs @@ -19,7 +19,7 @@ fn format_repeat_char(c: char, n: usize, f: &mut fmt::Formatter<'_>) -> fmt::Res } #[inline] -fn is_annotation_empty(annotation: &Annotation) -> bool { +fn is_annotation_empty(annotation: &Annotation<'_>) -> bool { annotation .label .iter() @@ -42,7 +42,7 @@ pub fn get_term_style(_color: bool) -> Box { Box::new(NoColorStylesheet) } -impl fmt::Display for DisplayList { +impl<'a> fmt::Display for DisplayList<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let lineno_width = self.body.iter().fold(0, |max, line| match line { DisplayLine::Source { @@ -76,7 +76,7 @@ impl fmt::Display for DisplayList { } } -impl DisplayList { +impl<'a> DisplayList<'a> { const ANONYMIZED_LINE_NUM: &'static str = "LL"; const ERROR_TXT: &'static str = "error"; const HELP_TXT: &'static str = "help"; @@ -123,7 +123,7 @@ impl DisplayList { fn format_label( &self, - label: &[DisplayTextFragment], + label: &[DisplayTextFragment<'_>], f: &mut fmt::Formatter<'_>, ) -> fmt::Result { let emphasis_style = self.stylesheet.get_style(StyleClass::Emphasis); @@ -139,7 +139,7 @@ impl DisplayList { fn format_annotation( &self, - annotation: &Annotation, + annotation: &Annotation<'_>, continuation: bool, in_source: bool, f: &mut fmt::Formatter<'_>, @@ -191,7 +191,7 @@ impl DisplayList { #[inline] fn format_source_line( &self, - line: &DisplaySourceLine, + line: &DisplaySourceLine<'_>, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { match line { @@ -259,7 +259,7 @@ impl DisplayList { #[inline] fn format_raw_line( &self, - line: &DisplayRawLine, + line: &DisplayRawLine<'_>, lineno_width: usize, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { @@ -318,7 +318,7 @@ impl DisplayList { #[inline] fn format_line( &self, - dl: &DisplayLine, + dl: &DisplayLine<'_>, lineno_width: usize, inline_marks_width: usize, f: &mut fmt::Formatter<'_>, diff --git a/src/snippet.rs b/src/snippet.rs index 0a703b2..bc7ba00 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -7,23 +7,23 @@ //! //! Snippet { //! title: Some(Annotation { -//! label: Some("mismatched types".to_string()), +//! label: Some("mismatched types"), //! id: None, //! annotation_type: AnnotationType::Error, //! }), //! footer: vec![], //! slices: vec![ //! Slice { -//! source: "Foo".to_string(), +//! source: "Foo", //! line_start: 51, -//! origin: Some("src/format.rs".to_string()), +//! origin: Some("src/format.rs"), //! fold: false, //! annotations: vec![], //! }, //! Slice { -//! source: "Faa".to_string(), +//! source: "Faa", //! line_start: 129, -//! origin: Some("src/display.rs".to_string()), +//! origin: Some("src/display.rs"), //! fold: false, //! annotations: vec![], //! }, @@ -34,11 +34,11 @@ use crate::display_list::FormatOptions; /// Primary structure provided for formatting -#[derive(Debug, Default, Clone)] -pub struct Snippet { - pub title: Option, - pub footer: Vec, - pub slices: Vec, +#[derive(Debug, Default)] +pub struct Snippet<'a> { + pub title: Option>, + pub footer: Vec>, + pub slices: Vec>, pub opt: FormatOptions, } @@ -47,12 +47,12 @@ pub struct Snippet { /// /// One `Slice` is meant to represent a single, continuous, /// slice of source code that you want to annotate. -#[derive(Debug, Clone)] -pub struct Slice { - pub source: String, +#[derive(Debug)] +pub struct Slice<'a> { + pub source: &'a str, pub line_start: usize, - pub origin: Option, - pub annotations: Vec, + pub origin: Option<&'a str>, + pub annotations: Vec>, /// If set explicitly to `true`, the snippet will fold /// parts of the slice that don't contain any annotations. pub fold: bool, @@ -71,18 +71,18 @@ pub enum AnnotationType { } /// An annotation for a `Slice`. -#[derive(Debug, Clone)] -pub struct SourceAnnotation { +#[derive(Debug)] +pub struct SourceAnnotation<'a> { pub range: (usize, usize), - pub label: String, + pub label: &'a str, pub annotation_type: AnnotationType, } /// An annotation for a `Snippet`. -#[derive(Debug, Clone)] -pub struct Annotation { +#[derive(Debug)] +pub struct Annotation<'a> { /// Identifier of the annotation. Usually error code like "E0308". - pub id: Option, - pub label: Option, + pub id: Option<&'a str>, + pub label: Option<&'a str>, pub annotation_type: AnnotationType, } diff --git a/tests/dl_from_snippet.rs b/tests/dl_from_snippet.rs index 993d336..2ed9902 100644 --- a/tests/dl_from_snippet.rs +++ b/tests/dl_from_snippet.rs @@ -1,11 +1,12 @@ +use annotate_snippets::display_list::DisplayList; use annotate_snippets::{display_list as dl, formatter::get_term_style, snippet}; #[test] fn test_format_title() { let input = snippet::Snippet { title: Some(snippet::Annotation { - id: Some("E0001".to_string()), - label: Some("This is a title".to_string()), + id: Some("E0001"), + label: Some("This is a title"), annotation_type: snippet::AnnotationType::Error, }), footer: vec![], @@ -16,9 +17,9 @@ fn test_format_title() { body: vec![dl::DisplayLine::Raw(dl::DisplayRawLine::Annotation { annotation: dl::Annotation { annotation_type: dl::DisplayAnnotationType::Error, - id: Some("E0001".to_string()), + id: Some("E0001"), label: vec![dl::DisplayTextFragment { - content: "This is a title".to_string(), + content: "This is a title", style: dl::DisplayTextStyle::Emphasis, }], }, @@ -33,14 +34,14 @@ fn test_format_title() { #[test] fn test_format_slice() { - let line_1 = "This is line 1".to_string(); - let line_2 = "This is line 2".to_string(); - let source = vec![line_1.clone(), line_2.clone()].join("\n"); + let line_1 = "This is line 1"; + let line_2 = "This is line 2"; + let source = vec![line_1, line_2].join("\n"); let input = snippet::Snippet { title: None, footer: vec![], slices: vec![snippet::Slice { - source: source.clone(), + source: &source, line_start: 5402, origin: None, annotations: vec![], @@ -59,7 +60,7 @@ fn test_format_slice() { lineno: Some(5402), inline_marks: vec![], line: dl::DisplaySourceLine::Content { - text: line_1.clone(), + text: line_1, range: (0, line_1.len()), }, }, @@ -85,25 +86,25 @@ fn test_format_slice() { #[test] fn test_format_slices_continuation() { - let src_0 = "This is slice 1".to_string(); + let src_0 = "This is slice 1"; let src_0_len = src_0.len(); - let src_1 = "This is slice 2".to_string(); + let src_1 = "This is slice 2"; let src_1_len = src_1.len(); let input = snippet::Snippet { title: None, footer: vec![], slices: vec![ snippet::Slice { - source: src_0.clone(), + source: src_0, line_start: 5402, - origin: Some("file1.rs".to_string()), + origin: Some("file1.rs"), annotations: vec![], fold: false, }, snippet::Slice { - source: src_1.clone(), + source: src_1, line_start: 2, - origin: Some("file2.rs".to_string()), + origin: Some("file2.rs"), annotations: vec![], fold: false, }, @@ -113,7 +114,7 @@ fn test_format_slices_continuation() { let output = dl::DisplayList { body: vec![ dl::DisplayLine::Raw(dl::DisplayRawLine::Origin { - path: "file1.rs".to_string(), + path: "file1.rs", pos: None, header_type: dl::DisplayHeaderType::Initial, }), @@ -136,7 +137,7 @@ fn test_format_slices_continuation() { line: dl::DisplaySourceLine::Empty, }, dl::DisplayLine::Raw(dl::DisplayRawLine::Origin { - path: "file2.rs".to_string(), + path: "file2.rs", pos: None, header_type: dl::DisplayHeaderType::Continuation, }), @@ -167,21 +168,21 @@ fn test_format_slices_continuation() { #[test] fn test_format_slice_annotation_standalone() { - let line_1 = "This is line 1".to_string(); - let line_2 = "This is line 2".to_string(); - let source = vec![line_1.clone(), line_2.clone()].join("\n"); + let line_1 = "This is line 1"; + let line_2 = "This is line 2"; + let source = vec![line_1, line_2].join("\n"); // In line 2 let range = (22, 24); let input = snippet::Snippet { title: None, footer: vec![], slices: vec![snippet::Slice { - source: source.clone(), + source: &source, line_start: 5402, origin: None, annotations: vec![snippet::SourceAnnotation { range, - label: "Test annotation".to_string(), + label: "Test annotation", annotation_type: snippet::AnnotationType::Info, }], fold: false, @@ -200,7 +201,7 @@ fn test_format_slice_annotation_standalone() { inline_marks: vec![], line: dl::DisplaySourceLine::Content { range: (0, line_1.len()), - text: line_1.clone(), + text: line_1, }, }, dl::DisplayLine::Source { @@ -219,7 +220,7 @@ fn test_format_slice_annotation_standalone() { annotation_type: dl::DisplayAnnotationType::Info, id: None, label: vec![dl::DisplayTextFragment { - content: "Test annotation".to_string(), + content: "Test annotation", style: dl::DisplayTextStyle::Regular, }], }, @@ -246,7 +247,7 @@ fn test_format_label() { title: None, footer: vec![snippet::Annotation { id: None, - label: Some("This __is__ a title".to_string()), + label: Some("This __is__ a title"), annotation_type: snippet::AnnotationType::Error, }], slices: vec![], @@ -259,15 +260,15 @@ fn test_format_label() { id: None, label: vec![ dl::DisplayTextFragment { - content: "This ".to_string(), + content: "This ", style: dl::DisplayTextStyle::Regular, }, dl::DisplayTextFragment { - content: "is".to_string(), + content: "is", style: dl::DisplayTextStyle::Emphasis, }, dl::DisplayTextFragment { - content: " a title".to_string(), + content: " a title", style: dl::DisplayTextStyle::Regular, }, ], @@ -284,8 +285,8 @@ fn test_format_label() { #[test] #[should_panic] fn test_i26() { - let source = "short".to_string(); - let label = "label".to_string(); + let source = "short"; + let label = "label"; let input = snippet::Snippet { title: None, footer: vec![], @@ -305,3 +306,96 @@ fn test_i26() { let _ = dl::DisplayList::from(input); } + +#[test] +fn test_i_29() { + let snippets = snippet::Snippet { + title: Some(snippet::Annotation { + id: None, + label: Some("oops"), + annotation_type: snippet::AnnotationType::Error, + }), + footer: vec![], + slices: vec![snippet::Slice { + source: "First line\r\nSecond oops line", + line_start: 1, + origin: Some(""), + annotations: vec![snippet::SourceAnnotation { + range: (19, 23), + label: "oops", + annotation_type: snippet::AnnotationType::Error, + }], + fold: true, + }], + opt: Default::default(), + }; + + let expected = DisplayList { + body: vec![ + dl::DisplayLine::Raw(dl::DisplayRawLine::Annotation { + annotation: dl::Annotation { + annotation_type: dl::DisplayAnnotationType::Error, + id: None, + label: vec![dl::DisplayTextFragment { + content: "oops", + style: dl::DisplayTextStyle::Emphasis, + }], + }, + source_aligned: false, + continuation: false, + }), + dl::DisplayLine::Raw(dl::DisplayRawLine::Origin { + path: "", + pos: Some((2, 8)), + header_type: dl::DisplayHeaderType::Initial, + }), + dl::DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: dl::DisplaySourceLine::Empty, + }, + dl::DisplayLine::Source { + lineno: Some(1), + inline_marks: vec![], + line: dl::DisplaySourceLine::Content { + text: "First line", + range: (0, 10), + }, + }, + dl::DisplayLine::Source { + lineno: Some(2), + inline_marks: vec![], + line: dl::DisplaySourceLine::Content { + text: "Second oops line", + range: (12, 28), + }, + }, + dl::DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: dl::DisplaySourceLine::Annotation { + annotation: dl::Annotation { + annotation_type: dl::DisplayAnnotationType::None, + id: None, + label: vec![dl::DisplayTextFragment { + content: "oops", + style: dl::DisplayTextStyle::Regular, + }], + }, + range: (7, 11), + annotation_type: dl::DisplayAnnotationType::Error, + annotation_part: dl::DisplayAnnotationPart::Standalone, + }, + }, + dl::DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: dl::DisplaySourceLine::Empty, + }, + ], + stylesheet: get_term_style(false), + anonymized_line_numbers: false, + }; + + assert_eq!(DisplayList::from(snippets), expected); +} diff --git a/tests/fixtures/no-color/multiline_annotation.toml b/tests/fixtures/no-color/multiline_annotation.toml new file mode 100644 index 0000000..bdb577f --- /dev/null +++ b/tests/fixtures/no-color/multiline_annotation.toml @@ -0,0 +1,40 @@ +[[slices]] +source = """ +) -> Option { + for ann in annotations { + match (ann.range.0, ann.range.1) { + (None, None) => continue, + (Some(start), Some(end)) if start > end_index || end < start_index => continue, + (Some(start), Some(end)) if start >= start_index && end <= end_index => { + let label = if let Some(ref label) = ann.label { + format!(" {}", label) + } else { + String::from("") + }; + + return Some(format!( + "{}{}{}", + " ".repeat(start - start_index), + "^".repeat(end - start), + label + )); + } + _ => continue, + } + } +""" +line_start = 51 +origin = "src/format.rs" +fold = true +[[slices.annotations]] +label = "expected `std::option::Option` because of return type" +annotation_type = "Warning" +range = [5, 19] +[[slices.annotations]] +label = "expected enum `std::option::Option`, found ()" +annotation_type = "Error" +range = [22, 765] +[title] +label = "mismatched types" +id = "E0308" +annotation_type = "Error" diff --git a/tests/fixtures/no-color/multiline_annotation.yaml b/tests/fixtures/no-color/multiline_annotation.yaml deleted file mode 100644 index 0622764..0000000 --- a/tests/fixtures/no-color/multiline_annotation.yaml +++ /dev/null @@ -1,38 +0,0 @@ -slices: - - source: |- - ) -> Option { - for ann in annotations { - match (ann.range.0, ann.range.1) { - (None, None) => continue, - (Some(start), Some(end)) if start > end_index || end < start_index => continue, - (Some(start), Some(end)) if start >= start_index && end <= end_index => { - let label = if let Some(ref label) = ann.label { - format!(" {}", label) - } else { - String::from("") - }; - - return Some(format!( - "{}{}{}", - " ".repeat(start - start_index), - "^".repeat(end - start), - label - )); - } - _ => continue, - } - } - line_start: 51 - origin: "src/format.rs" - fold: true - annotations: - - label: expected `std::option::Option` because of return type - annotation_type: Warning - range: [5, 19] - - label: expected enum `std::option::Option`, found () - annotation_type: Error - range: [22, 765] -title: - label: mismatched types - id: E0308 - annotation_type: Error diff --git a/tests/fixtures/no-color/multiline_annotation2.toml b/tests/fixtures/no-color/multiline_annotation2.toml new file mode 100644 index 0000000..6ec0b1f --- /dev/null +++ b/tests/fixtures/no-color/multiline_annotation2.toml @@ -0,0 +1,18 @@ +[[slices]] +source = """ + if let DisplayLine::Source { + ref mut inline_marks, + } = body[body_idx] +""" +line_start = 139 +origin = "src/display_list.rs" +fold = false +[[slices.annotations]] +label = "missing fields `lineno`, `content`" +annotation_type = "Error" +range = [31, 127] + +[title] +label = "pattern does not mention fields `lineno`, `content`" +id = "E0027" +annotation_type = "Error" diff --git a/tests/fixtures/no-color/multiline_annotation2.yaml b/tests/fixtures/no-color/multiline_annotation2.yaml deleted file mode 100644 index c140ffe..0000000 --- a/tests/fixtures/no-color/multiline_annotation2.yaml +++ /dev/null @@ -1,16 +0,0 @@ -slices: - - source: |1 - if let DisplayLine::Source { - ref mut inline_marks, - } = body[body_idx] - line_start: 139 - origin: "src/display_list.rs" - fold: false - annotations: - - label: missing fields `lineno`, `content` - annotation_type: Error - range: [31, 127] -title: - label: pattern does not mention fields `lineno`, `content` - id: E0027 - annotation_type: Error diff --git a/tests/fixtures/no-color/multiline_annotation3.toml b/tests/fixtures/no-color/multiline_annotation3.toml new file mode 100644 index 0000000..21bbcd8 --- /dev/null +++ b/tests/fixtures/no-color/multiline_annotation3.toml @@ -0,0 +1,18 @@ +[[slices]] +source = """ +This is an exampl +e of an edge case of an annotation overflowing +to exactly one character on next line. +""" +line_start = 26 +origin = "foo.txt" +fold = false +[[slices.annotations]] +label = "this should not be on separate lines" +annotation_type = "Error" +range = [11, 18] + +[title] +label = "spacing error found" +id = "E####" +annotation_type = "Error" diff --git a/tests/fixtures/no-color/multiline_annotation3.yaml b/tests/fixtures/no-color/multiline_annotation3.yaml deleted file mode 100644 index a807512..0000000 --- a/tests/fixtures/no-color/multiline_annotation3.yaml +++ /dev/null @@ -1,16 +0,0 @@ -slices: - - source: |- - This is an exampl - e of an edge case of an annotation overflowing - to exactly one character on next line. - line_start: 26 - origin: foo.txt - fold: false - annotations: - - label: this should not be on separate lines - annotation_type: Error - range: [11, 18] -title: - label: spacing error found - id: E#### - annotation_type: Error diff --git a/tests/fixtures/no-color/multiple_annotations.toml b/tests/fixtures/no-color/multiple_annotations.toml new file mode 100644 index 0000000..84efc5f --- /dev/null +++ b/tests/fixtures/no-color/multiple_annotations.toml @@ -0,0 +1,25 @@ +[[slices]] +source = """ +fn add_title_line(result: &mut Vec, main_annotation: Option<&Annotation>) { + if let Some(annotation) = main_annotation { + result.push(format_title_line( + &annotation.annotation_type, + None, + &annotation.label, + )); + } +} +""" +line_start = 96 +[[slices.annotations]] +label = "Variable defined here" +annotation_type = "Error" +range = [100, 110] +[[slices.annotations]] +label = "Referenced here" +annotation_type = "Error" +range = [184, 194] +[[slices.annotations]] +label = "Referenced again here" +annotation_type = "Error" +range = [243, 253] diff --git a/tests/fixtures/no-color/multiple_annotations.yaml b/tests/fixtures/no-color/multiple_annotations.yaml deleted file mode 100644 index 4ea7f9c..0000000 --- a/tests/fixtures/no-color/multiple_annotations.yaml +++ /dev/null @@ -1,23 +0,0 @@ -slices: - - source: |- - fn add_title_line(result: &mut Vec, main_annotation: Option<&Annotation>) { - if let Some(annotation) = main_annotation { - result.push(format_title_line( - &annotation.annotation_type, - None, - &annotation.label, - )); - } - } - line_start: 96 - annotations: - - label: Variable defined here - annotation_type: Error - range: [100, 110] - - label: Referenced here - annotation_type: Error - range: [184, 194] - - label: Referenced again here - annotation_type: Error - range: [243, 253] -title: null diff --git a/tests/fixtures/no-color/simple.toml b/tests/fixtures/no-color/simple.toml new file mode 100644 index 0000000..6c38674 --- /dev/null +++ b/tests/fixtures/no-color/simple.toml @@ -0,0 +1,18 @@ +[[slices]] +source = """ + }) + + for line in &self.body {""" +line_start = 169 +origin = "src/format_color.rs" +[[slices.annotations]] +label = "unexpected token" +annotation_type = "Error" +range = [20, 23] +[[slices.annotations]] +label = "expected one of `.`, `;`, `?`, or an operator here" +annotation_type = "Warning" +range = [10, 11] +[title] +label = "expected one of `.`, `;`, `?`, or an operator, found `for`" +annotation_type = "Error" diff --git a/tests/fixtures/no-color/simple.yaml b/tests/fixtures/no-color/simple.yaml deleted file mode 100644 index 697789e..0000000 --- a/tests/fixtures/no-color/simple.yaml +++ /dev/null @@ -1,17 +0,0 @@ -slices: - - source: |1 - }) - - for line in &self.body { - line_start: 169 - origin: src/format_color.rs - annotations: - - label: unexpected token - annotation_type: Error - range: [20, 23] - - label: expected one of `.`, `;`, `?`, or an operator here - annotation_type: Warning - range: [10, 11] -title: - label: expected one of `.`, `;`, `?`, or an operator, found `for` - annotation_type: Error diff --git a/tests/fixtures_test.rs b/tests/fixtures_test.rs index 905b18f..e471521 100644 --- a/tests/fixtures_test.rs +++ b/tests/fixtures_test.rs @@ -4,8 +4,7 @@ mod snippet; use crate::snippet::SnippetDef; use annotate_snippets::{display_list::DisplayList, snippet::Snippet}; use glob::glob; -use serde::Deserialize; -use std::{error::Error, fs::File, io, io::prelude::*, path::Path}; +use std::{error::Error, fs::File, io, io::prelude::*}; fn read_file(path: &str) -> Result { let mut f = File::open(path)?; @@ -14,24 +13,20 @@ fn read_file(path: &str) -> Result { Ok(s.trim_end().to_string()) } -fn read_fixture>(path: P) -> Result> { - #[derive(Deserialize)] - struct Wrapper(#[serde(with = "SnippetDef")] Snippet); - - let file = File::open(path)?; - let u = serde_yaml::from_reader(file).map(|Wrapper(a)| a)?; - Ok(u) +fn read_fixture<'de>(src: &'de str) -> Result, Box> { + Ok(toml::from_str(src).map(|a: SnippetDef| a.into())?) } #[test] fn test_fixtures() { - for entry in glob("./tests/fixtures/no-color/**/*.yaml").expect("Failed to read glob pattern") { + for entry in glob("./tests/fixtures/no-color/**/*.toml").expect("Failed to read glob pattern") { let p = entry.expect("Error while getting an entry"); - let path_in = p.to_str().expect("Can't print path"); - let path_out = path_in.replace(".yaml", ".txt"); + let path_in = p.to_str().expect("Can't print path"); + let path_out = path_in.replace(".toml", ".txt"); - let snippet = read_fixture(path_in).expect("Failed to read file"); + let src = read_file(&path_in).expect("Failed to read file"); + let snippet = read_fixture(&src).expect("Failed to read file"); let expected_out = read_file(&path_out).expect("Failed to read file"); let dl = DisplayList::from(snippet); diff --git a/tests/formatter.rs b/tests/formatter.rs index f0a6bae..5c7211d 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1,4 +1,5 @@ use annotate_snippets::display_list::*; +use annotate_snippets::snippet::{self, Snippet}; #[test] fn test_source_empty() { @@ -18,7 +19,7 @@ fn test_source_content() { lineno: Some(56), inline_marks: vec![], line: DisplaySourceLine::Content { - text: "This is an example".to_string(), + text: "This is an example", range: (0, 19), }, }, @@ -26,7 +27,7 @@ fn test_source_content() { lineno: Some(57), inline_marks: vec![], line: DisplaySourceLine::Content { - text: "of content lines".to_string(), + text: "of content lines", range: (0, 19), }, }, @@ -49,7 +50,7 @@ fn test_source_annotation_standalone_singleline() { annotation_type: DisplayAnnotationType::None, id: None, label: vec![DisplayTextFragment { - content: String::from("Example string"), + content: "Example string", style: DisplayTextStyle::Regular, }], }, @@ -73,7 +74,7 @@ fn test_source_annotation_standalone_multiline() { annotation_type: DisplayAnnotationType::Help, id: None, label: vec![DisplayTextFragment { - content: String::from("Example string"), + content: "Example string", style: DisplayTextStyle::Regular, }], }, @@ -90,7 +91,7 @@ fn test_source_annotation_standalone_multiline() { annotation_type: DisplayAnnotationType::Help, id: None, label: vec![DisplayTextFragment { - content: String::from("Second line"), + content: "Second line", style: DisplayTextStyle::Regular, }], }, @@ -118,7 +119,7 @@ fn test_source_annotation_standalone_multi_annotation() { annotation_type: DisplayAnnotationType::Info, id: None, label: vec![DisplayTextFragment { - content: String::from("Example string"), + content: "Example string", style: DisplayTextStyle::Regular, }], }, @@ -135,7 +136,7 @@ fn test_source_annotation_standalone_multi_annotation() { annotation_type: DisplayAnnotationType::Info, id: None, label: vec![DisplayTextFragment { - content: String::from("Second line"), + content: "Second line", style: DisplayTextStyle::Regular, }], }, @@ -152,7 +153,7 @@ fn test_source_annotation_standalone_multi_annotation() { annotation_type: DisplayAnnotationType::Warning, id: None, label: vec![DisplayTextFragment { - content: String::from("This is a note"), + content: "This is a note", style: DisplayTextStyle::Regular, }], }, @@ -169,7 +170,7 @@ fn test_source_annotation_standalone_multi_annotation() { annotation_type: DisplayAnnotationType::Warning, id: None, label: vec![DisplayTextFragment { - content: String::from("Second line of the warning"), + content: "Second line of the warning", style: DisplayTextStyle::Regular, }], }, @@ -186,7 +187,7 @@ fn test_source_annotation_standalone_multi_annotation() { annotation_type: DisplayAnnotationType::Info, id: None, label: vec![DisplayTextFragment { - content: String::from("This is an info"), + content: "This is an info", style: DisplayTextStyle::Regular, }], }, @@ -203,7 +204,7 @@ fn test_source_annotation_standalone_multi_annotation() { annotation_type: DisplayAnnotationType::Help, id: None, label: vec![DisplayTextFragment { - content: String::from("This is help"), + content: "This is help", style: DisplayTextStyle::Regular, }], }, @@ -220,7 +221,7 @@ fn test_source_annotation_standalone_multi_annotation() { annotation_type: DisplayAnnotationType::None, id: None, label: vec![DisplayTextFragment { - content: String::from("This is an annotation of type none"), + content: "This is an annotation of type none", style: DisplayTextStyle::Regular, }], }, @@ -240,7 +241,7 @@ fn test_fold_line() { lineno: Some(5), inline_marks: vec![], line: DisplaySourceLine::Content { - text: "This is line 5".to_string(), + text: "This is line 5", range: (0, 19), }, }, @@ -251,7 +252,7 @@ fn test_fold_line() { lineno: Some(10021), inline_marks: vec![], line: DisplaySourceLine::Content { - text: "... and now we're at line 10021".to_string(), + text: "... and now we're at line 10021", range: (0, 19), }, }, @@ -266,7 +267,7 @@ fn test_fold_line() { #[test] fn test_raw_origin_initial_nopos() { let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { - path: "src/test.rs".to_string(), + path: "src/test.rs", pos: None, header_type: DisplayHeaderType::Initial, })]); @@ -277,7 +278,7 @@ fn test_raw_origin_initial_nopos() { #[test] fn test_raw_origin_initial_pos() { let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { - path: "src/test.rs".to_string(), + path: "src/test.rs", pos: Some((23, 15)), header_type: DisplayHeaderType::Initial, })]); @@ -288,7 +289,7 @@ fn test_raw_origin_initial_pos() { #[test] fn test_raw_origin_continuation() { let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { - path: "src/test.rs".to_string(), + path: "src/test.rs", pos: Some((23, 15)), header_type: DisplayHeaderType::Continuation, })]); @@ -301,9 +302,9 @@ fn test_raw_annotation_unaligned() { let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Error, - id: Some("E0001".to_string()), + id: Some("E0001"), label: vec![DisplayTextFragment { - content: String::from("This is an error"), + content: "This is an error", style: DisplayTextStyle::Regular, }], }, @@ -320,9 +321,9 @@ fn test_raw_annotation_unaligned_multiline() { DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Warning, - id: Some("E0001".to_string()), + id: Some("E0001"), label: vec![DisplayTextFragment { - content: String::from("This is an error"), + content: "This is an error", style: DisplayTextStyle::Regular, }], }, @@ -332,9 +333,9 @@ fn test_raw_annotation_unaligned_multiline() { DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Warning, - id: Some("E0001".to_string()), + id: Some("E0001"), label: vec![DisplayTextFragment { - content: String::from("Second line of the error"), + content: "Second line of the error", style: DisplayTextStyle::Regular, }], }, @@ -354,9 +355,9 @@ fn test_raw_annotation_aligned() { let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Error, - id: Some("E0001".to_string()), + id: Some("E0001"), label: vec![DisplayTextFragment { - content: String::from("This is an error"), + content: "This is an error", style: DisplayTextStyle::Regular, }], }, @@ -373,9 +374,9 @@ fn test_raw_annotation_aligned_multiline() { DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Warning, - id: Some("E0001".to_string()), + id: Some("E0001"), label: vec![DisplayTextFragment { - content: String::from("This is an error"), + content: "This is an error", style: DisplayTextStyle::Regular, }], }, @@ -385,9 +386,9 @@ fn test_raw_annotation_aligned_multiline() { DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Warning, - id: Some("E0001".to_string()), + id: Some("E0001"), label: vec![DisplayTextFragment { - content: String::from("Second line of the error"), + content: "Second line of the error", style: DisplayTextStyle::Regular, }], }, @@ -410,7 +411,7 @@ fn test_different_annotation_types() { annotation_type: DisplayAnnotationType::Note, id: None, label: vec![DisplayTextFragment { - content: String::from("This is a note"), + content: "This is a note", style: DisplayTextStyle::Regular, }], }, @@ -422,7 +423,7 @@ fn test_different_annotation_types() { annotation_type: DisplayAnnotationType::None, id: None, label: vec![DisplayTextFragment { - content: String::from("This is just a string"), + content: "This is just a string", style: DisplayTextStyle::Regular, }], }, @@ -434,7 +435,7 @@ fn test_different_annotation_types() { annotation_type: DisplayAnnotationType::None, id: None, label: vec![DisplayTextFragment { - content: String::from("Second line of none type annotation"), + content: "Second line of none type annotation", style: DisplayTextStyle::Regular, }], }, @@ -470,7 +471,7 @@ fn test_anon_lines() { lineno: Some(56), inline_marks: vec![], line: DisplaySourceLine::Content { - text: "This is an example".to_string(), + text: "This is an example", range: (0, 19), }, }, @@ -478,7 +479,7 @@ fn test_anon_lines() { lineno: Some(57), inline_marks: vec![], line: DisplaySourceLine::Content { - text: "of content lines".to_string(), + text: "of content lines", range: (0, 19), }, }, @@ -491,7 +492,7 @@ fn test_anon_lines() { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Content { - text: "abc".to_string(), + text: "abc", range: (0, 19), }, }, @@ -507,7 +508,7 @@ fn test_anon_lines() { #[test] fn test_raw_origin_initial_pos_anon_lines() { let mut dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { - path: "src/test.rs".to_string(), + path: "src/test.rs", pos: Some((23, 15)), header_type: DisplayHeaderType::Initial, })]); @@ -516,3 +517,36 @@ fn test_raw_origin_initial_pos_anon_lines() { dl.anonymized_line_numbers = true; assert_eq!(dl.to_string(), "--> src/test.rs:23:15"); } + +#[test] +fn test_i_29() { + let snippets = Snippet { + title: Some(snippet::Annotation { + id: None, + label: Some("oops"), + annotation_type: snippet::AnnotationType::Error, + }), + footer: vec![], + slices: vec![snippet::Slice { + source: "First line\r\nSecond oops line", + line_start: 1, + origin: Some(""), + annotations: vec![snippet::SourceAnnotation { + range: (19, 23), + label: "oops", + annotation_type: snippet::AnnotationType::Error, + }], + fold: true, + }], + opt: Default::default(), + }; + let expected = r#"error: oops + --> :2:8 + | +1 | First line +2 | Second oops line + | ^^^^ oops + |"#; + + assert_eq!(DisplayList::from(snippets).to_string(), expected); +} diff --git a/tests/snippet/mod.rs b/tests/snippet/mod.rs index 258c16f..c334874 100644 --- a/tests/snippet/mod.rs +++ b/tests/snippet/mod.rs @@ -6,19 +6,38 @@ use annotate_snippets::{ }; #[derive(Deserialize)] -#[serde(remote = "Snippet")] -pub struct SnippetDef { +pub struct SnippetDef<'a> { #[serde(deserialize_with = "deserialize_annotation")] #[serde(default)] - pub title: Option, + #[serde(borrow)] + pub title: Option>, #[serde(deserialize_with = "deserialize_annotations")] #[serde(default)] - pub footer: Vec, + #[serde(borrow)] + pub footer: Vec>, #[serde(deserialize_with = "deserialize_opt")] #[serde(default)] pub opt: FormatOptions, #[serde(deserialize_with = "deserialize_slices")] - pub slices: Vec, + #[serde(borrow)] + pub slices: Vec>, +} + +impl<'a> Into> for SnippetDef<'a> { + fn into(self) -> Snippet<'a> { + let SnippetDef { + title, + footer, + opt, + slices, + } = self; + Snippet { + title, + footer, + slices, + opt, + } + } } fn deserialize_opt<'de, D>(deserializer: D) -> Result @@ -38,34 +57,46 @@ pub struct FormatOptionsDef { pub anonymized_line_numbers: bool, } -fn deserialize_slices<'de, D>(deserializer: D) -> Result, D::Error> +fn deserialize_slices<'de, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, { #[derive(Deserialize)] - struct Wrapper(#[serde(with = "SliceDef")] Slice); + struct Wrapper<'a>( + #[serde(with = "SliceDef")] + #[serde(borrow)] + Slice<'a>, + ); let v = Vec::deserialize(deserializer)?; Ok(v.into_iter().map(|Wrapper(a)| a).collect()) } -fn deserialize_annotation<'de, D>(deserializer: D) -> Result, D::Error> +fn deserialize_annotation<'de, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, { #[derive(Deserialize)] - struct Wrapper(#[serde(with = "AnnotationDef")] Annotation); + struct Wrapper<'a>( + #[serde(with = "AnnotationDef")] + #[serde(borrow)] + Annotation<'a>, + ); Option::::deserialize(deserializer) .map(|opt_wrapped: Option| opt_wrapped.map(|wrapped: Wrapper| wrapped.0)) } -fn deserialize_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(#[serde(with = "AnnotationDef")] Annotation); + struct Wrapper<'a>( + #[serde(with = "AnnotationDef")] + #[serde(borrow)] + Annotation<'a>, + ); let v = Vec::deserialize(deserializer)?; Ok(v.into_iter().map(|Wrapper(a)| a).collect()) @@ -73,24 +104,31 @@ where #[derive(Deserialize)] #[serde(remote = "Slice")] -pub struct SliceDef { - pub source: String, +pub struct SliceDef<'a> { + #[serde(borrow)] + pub source: &'a str, pub line_start: usize, - pub origin: Option, + #[serde(borrow)] + pub origin: Option<&'a str>, #[serde(deserialize_with = "deserialize_source_annotations")] - pub annotations: Vec, + #[serde(borrow)] + pub annotations: Vec>, #[serde(default)] pub fold: bool, } fn deserialize_source_annotations<'de, D>( deserializer: D, -) -> Result, D::Error> +) -> Result>, D::Error> where D: Deserializer<'de>, { #[derive(Deserialize)] - struct Wrapper(#[serde(with = "SourceAnnotationDef")] SourceAnnotation); + struct Wrapper<'a>( + #[serde(with = "SourceAnnotationDef")] + #[serde(borrow)] + SourceAnnotation<'a>, + ); let v = Vec::deserialize(deserializer)?; Ok(v.into_iter().map(|Wrapper(a)| a).collect()) @@ -98,18 +136,21 @@ where #[derive(Serialize, Deserialize)] #[serde(remote = "SourceAnnotation")] -pub struct SourceAnnotationDef { +pub struct SourceAnnotationDef<'a> { pub range: (usize, usize), - pub label: String, + #[serde(borrow)] + pub label: &'a str, #[serde(with = "AnnotationTypeDef")] pub annotation_type: AnnotationType, } #[derive(Serialize, Deserialize)] #[serde(remote = "Annotation")] -pub struct AnnotationDef { - pub id: Option, - pub label: Option, +pub struct AnnotationDef<'a> { + #[serde(borrow)] + pub id: Option<&'a str>, + #[serde(borrow)] + pub label: Option<&'a str>, #[serde(with = "AnnotationTypeDef")] pub annotation_type: AnnotationType, }