diff --git a/Cargo.lock b/Cargo.lock index c15507c8..0d9659ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,13 +21,13 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" name = "annotate-snippets" version = "0.9.2" dependencies = [ + "anstyle", "criterion", "difference", "glob", "serde", "toml", "unicode-width", - "yansi-term", ] [[package]] @@ -688,12 +688,3 @@ name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "yansi-term" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1" -dependencies = [ - "winapi", -] diff --git a/Cargo.toml b/Cargo.toml index 86666519..ec39e99d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,8 @@ keywords = ["code", "analysis", "ascii", "errors", "debug"] maintenance = { status = "actively-developed" } [dependencies] +anstyle = "1.0.4" unicode-width = "0.1.11" -yansi-term = { version = "0.1.2", optional = true } [dev-dependencies] criterion = "0.5.1" @@ -23,7 +23,6 @@ difference = "2.0.0" glob = "0.3.1" serde = { version = "1.0.192", features = ["derive"] } toml = "0.5.11" -yansi-term = "0.1.2" [[bench]] name = "simple" @@ -31,4 +30,3 @@ harness = false [features] default = [] -color = ["yansi-term"] diff --git a/benches/simple.rs b/benches/simple.rs index 4c13a8f0..3a40bbf5 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -4,12 +4,9 @@ extern crate criterion; use criterion::{black_box, Criterion}; -use annotate_snippets::{ - display_list::{DisplayList, FormatOptions}, - snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, -}; +use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation}; -fn create_snippet() { +fn create_snippet(renderer: Renderer) { let snippet = Snippet { slices: vec![Slice { source: r#") -> Option { @@ -56,18 +53,15 @@ fn create_snippet() { annotation_type: AnnotationType::Error, }), footer: vec![], - opt: FormatOptions { - color: true, - ..Default::default() - }, }; - let dl = DisplayList::from(snippet); - let _result = dl.to_string(); + let _result = renderer.render(snippet).to_string(); } pub fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("format", |b| b.iter(|| black_box(create_snippet()))); + c.bench_function("format", |b| { + b.iter(|| black_box(create_snippet(Renderer::plain()))) + }); } criterion_group!(benches, criterion_benchmark); diff --git a/examples/expected_type.rs b/examples/expected_type.rs index 6f2a0d9a..bbd1fe64 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -1,7 +1,4 @@ -use annotate_snippets::{ - display_list::{DisplayList, FormatOptions}, - snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, -}; +use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation}; fn main() { let snippet = Snippet { @@ -32,12 +29,8 @@ fn main() { }, ], }], - opt: FormatOptions { - color: true, - ..Default::default() - }, }; - let dl = DisplayList::from(snippet); - println!("{}", dl); + let renderer = Renderer::plain(); + println!("{}", renderer.render(snippet)); } diff --git a/examples/footer.rs b/examples/footer.rs index f3c15c41..ca021198 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -1,7 +1,4 @@ -use annotate_snippets::{ - display_list::{DisplayList, FormatOptions}, - snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, -}; +use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation}; fn main() { let snippet = Snippet { @@ -28,12 +25,8 @@ fn main() { annotation_type: AnnotationType::Error, }], }], - opt: FormatOptions { - color: true, - ..Default::default() - }, }; - let dl = DisplayList::from(snippet); - println!("{}", dl); + let renderer = Renderer::plain(); + println!("{}", renderer.render(snippet)); } diff --git a/examples/format.rs b/examples/format.rs index 98b77a14..41f852ee 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -1,7 +1,4 @@ -use annotate_snippets::{ - display_list::{DisplayList, FormatOptions}, - snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, -}; +use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation}; fn main() { let snippet = Snippet { @@ -50,12 +47,8 @@ fn main() { annotation_type: AnnotationType::Error, }), footer: vec![], - opt: FormatOptions { - color: true, - ..Default::default() - }, }; - let dl = DisplayList::from(snippet); - println!("{}", dl); + let renderer = Renderer::plain(); + println!("{}", renderer.render(snippet)); } diff --git a/examples/multislice.rs b/examples/multislice.rs index 5675a07d..63ebb650 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -1,7 +1,4 @@ -use annotate_snippets::{ - display_list::{DisplayList, FormatOptions}, - snippet::{Annotation, AnnotationType, Slice, Snippet}, -}; +use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet}; fn main() { let snippet = Snippet { @@ -27,12 +24,8 @@ fn main() { annotations: vec![], }, ], - opt: FormatOptions { - color: true, - ..Default::default() - }, }; - let dl = DisplayList::from(snippet); - println!("{}", dl); + let renderer = Renderer::plain(); + println!("{}", renderer.render(snippet)); } diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs deleted file mode 100644 index 72bf9c77..00000000 --- a/src/formatter/mod.rs +++ /dev/null @@ -1,23 +0,0 @@ -pub mod style; - -use self::style::Stylesheet; - -#[cfg(feature = "color")] -use crate::stylesheets::color::AnsiTermStylesheet; -use crate::stylesheets::no_color::NoColorStylesheet; - -#[cfg(feature = "color")] -#[inline] -pub fn get_term_style(color: bool) -> Box { - if color { - Box::new(AnsiTermStylesheet) - } else { - Box::new(NoColorStylesheet) - } -} - -#[cfg(not(feature = "color"))] -#[inline] -pub fn get_term_style(_color: bool) -> Box { - Box::new(NoColorStylesheet) -} diff --git a/src/formatter/style.rs b/src/formatter/style.rs deleted file mode 100644 index 3fc01c19..00000000 --- a/src/formatter/style.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! Set of structures required to implement a stylesheet -//! -//! In order to provide additional styling information for the -//! formatter, a structs can implement `Stylesheet` and `Style` -//! traits. -//! -use std::fmt; - -/// StyleClass is a collection of named variants of style classes -pub enum StyleClass { - /// Message indicating an error. - Error, - /// Message indicating a warning. - Warning, - /// Message indicating an information. - Info, - /// Message indicating a note. - Note, - /// Message indicating a help. - Help, - - /// Style for line numbers. - LineNo, - - /// Parts of the text that are to be emphasised. - Emphasis, - - /// Parts of the text that are regular. Usually a no-op. - None, -} - -/// This trait implements a return value for the `Stylesheet::get_style`. -pub trait Style { - /// The method used to write text with formatter - fn paint(&self, text: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result; - /// The method used to write display function with formatter - fn paint_fn<'a>( - &self, - c: Box) -> fmt::Result + 'a>, - f: &mut fmt::Formatter<'_>, - ) -> fmt::Result; - /// The method used by the `Formatter` to display the message in bold font. - fn bold(&self) -> Box; -} - -/// Trait to annotate structs that can provide `Style` implementations for -/// every `StyleClass` variant. -pub trait Stylesheet { - /// Returns a `Style` implementer based on the requested `StyleClass` variant. - fn get_style(&self, class: StyleClass) -> Box; -} diff --git a/src/lib.rs b/src/lib.rs index d5813672..80eac530 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,29 +26,24 @@ //! The crate uses a three stage process with two conversions between states: //! //! ```text -//! Snippet --> DisplayList --> String +//! Snippet --> Renderer --> impl Display //! ``` //! -//! The input type - [Snippet](self::snippet) is a structure designed +//! The input type - [Snippet] is a structure designed //! to align with likely output from any parser whose code snippet is to be //! annotated. //! -//! The middle structure - [DisplayList](self::display_list) is a -//! structure designed to store the snippet data converted into a vector -//! of lines containing semantic information about each line. -//! This structure is the easiest to manipulate and organize. +//! The middle structure - [Renderer] is a structure designed +//! to convert a snippet into an internal structure that is designed to store +//! the snippet data in a way that is easy to format. +//! [Renderer] also handles the user-configurable formatting +//! options, such as color, or margins. //! //! Finally, `impl Display` into a final `String` output. -//! -//! A user of the crate may choose to provide their own equivalent of the input -//! structure with an `Into` trait. -//! -//! A user of the crate may also choose to provide their own formatter logic, -//! to convert a `DisplayList` into a `String`, or just a `Stylesheet` to -//! use the crate's formatting logic, but with a custom stylesheet. -// TODO: check documentation -pub mod display_list; -pub mod formatter; -pub mod snippet; -pub mod stylesheets; +pub mod renderer; +mod snippet; + +#[doc(inline)] +pub use renderer::Renderer; +pub use snippet::*; diff --git a/src/display_list/mod.rs b/src/renderer/display_list.rs similarity index 52% rename from src/display_list/mod.rs rename to src/renderer/display_list.rs index 1ad328aa..02e14a50 100644 --- a/src/display_list/mod.rs +++ b/src/renderer/display_list.rs @@ -31,33 +31,20 @@ //! styling. //! //! The above snippet has been built out of the following structure: -use std::cmp::{max, min}; +use crate::snippet; use std::fmt::{Display, Write}; use std::{cmp, fmt}; -use crate::formatter::style::{Style, StyleClass}; -use crate::formatter::{get_term_style, style::Stylesheet}; -use crate::snippet; +use crate::renderer::{stylesheet::Stylesheet, Margin, Style}; /// List of lines to be displayed. -pub struct DisplayList<'a> { +pub(crate) struct DisplayList<'a> { pub body: Vec>, - pub stylesheet: Box, + pub stylesheet: &'a Stylesheet, pub anonymized_line_numbers: bool, pub margin: Option, } -impl<'a> From>> for DisplayList<'a> { - fn from(body: Vec>) -> DisplayList<'a> { - Self { - body, - anonymized_line_numbers: false, - stylesheet: get_term_style(false), - margin: None, - } - } -} - impl<'a> PartialEq for DisplayList<'a> { fn eq(&self, other: &Self) -> bool { self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers @@ -107,14 +94,23 @@ impl<'a> Display for DisplayList<'a> { } } -impl<'a> From> for DisplayList<'a> { - fn from( +impl<'a> DisplayList<'a> { + const ANONYMIZED_LINE_NUM: &'static str = "LL"; + const ERROR_TXT: &'static str = "error"; + const HELP_TXT: &'static str = "help"; + const INFO_TXT: &'static str = "info"; + const NOTE_TXT: &'static str = "note"; + const WARNING_TXT: &'static str = "warning"; + + pub(crate) fn new( snippet::Snippet { title, footer, slices, - opt, }: snippet::Snippet<'a>, + stylesheet: &'a Stylesheet, + anonymized_line_numbers: bool, + margin: Option, ) -> DisplayList<'a> { let mut body = vec![]; if let Some(annotation) = title { @@ -126,7 +122,7 @@ impl<'a> From> for DisplayList<'a> { slice, idx == 0, !footer.is_empty(), - opt.margin, + margin, )); } @@ -134,28 +130,13 @@ impl<'a> From> for DisplayList<'a> { body.append(&mut format_annotation(annotation)); } - let FormatOptions { - color, - anonymized_line_numbers, - margin, - } = opt; - Self { body, - stylesheet: get_term_style(color), + stylesheet, anonymized_line_numbers, margin, } } -} - -impl<'a> DisplayList<'a> { - const ANONYMIZED_LINE_NUM: &'static str = "LL"; - const ERROR_TXT: &'static str = "error"; - const HELP_TXT: &'static str = "help"; - const INFO_TXT: &'static str = "info"; - const NOTE_TXT: &'static str = "note"; - const WARNING_TXT: &'static str = "warning"; #[inline] fn format_annotation_type( @@ -183,15 +164,15 @@ impl<'a> DisplayList<'a> { } } - fn get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> Box { - self.stylesheet.get_style(match annotation_type { - DisplayAnnotationType::Error => StyleClass::Error, - DisplayAnnotationType::Warning => StyleClass::Warning, - DisplayAnnotationType::Info => StyleClass::Info, - DisplayAnnotationType::Note => StyleClass::Note, - DisplayAnnotationType::Help => StyleClass::Help, - DisplayAnnotationType::None => StyleClass::None, - }) + fn get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> &Style { + match annotation_type { + DisplayAnnotationType::Error => self.stylesheet.error(), + DisplayAnnotationType::Warning => self.stylesheet.warning(), + DisplayAnnotationType::Info => self.stylesheet.info(), + DisplayAnnotationType::Note => self.stylesheet.note(), + DisplayAnnotationType::Help => self.stylesheet.help(), + DisplayAnnotationType::None => self.stylesheet.none(), + } } fn format_label( @@ -199,12 +180,20 @@ impl<'a> DisplayList<'a> { label: &[DisplayTextFragment<'_>], f: &mut fmt::Formatter<'_>, ) -> fmt::Result { - let emphasis_style = self.stylesheet.get_style(StyleClass::Emphasis); + let emphasis_style = self.stylesheet.emphasis(); for fragment in label { match fragment.style { DisplayTextStyle::Regular => fragment.content.fmt(f)?, - DisplayTextStyle::Emphasis => emphasis_style.paint(fragment.content, f)?, + DisplayTextStyle::Emphasis => { + write!( + f, + "{}{}{}", + emphasis_style.render(), + fragment.content, + emphasis_style.render_reset() + )?; + } } } Ok(()) @@ -231,27 +220,21 @@ impl<'a> DisplayList<'a> { if formatted_len == 0 { self.format_label(&annotation.label, f) } else { - color.paint_fn( - Box::new(|f| { - Self::format_annotation_type(&annotation.annotation_type, f)?; - if let Some(id) = &annotation.id { - f.write_char('[')?; - f.write_str(id)?; - f.write_char(']')?; - } - Ok(()) - }), - f, - )?; + write!(f, "{}", color.render())?; + Self::format_annotation_type(&annotation.annotation_type, f)?; + if let Some(id) = &annotation.id { + f.write_char('[')?; + f.write_str(id)?; + f.write_char(']')?; + } + write!(f, "{}", color.render_reset())?; + if !is_annotation_empty(annotation) { if in_source { - color.paint_fn( - Box::new(|f| { - f.write_str(": ")?; - self.format_label(&annotation.label, f) - }), - f, - )?; + write!(f, "{}", color.render())?; + f.write_str(": ")?; + self.format_label(&annotation.label, f)?; + write!(f, "{}", color.render_reset())?; } else { f.write_str(": ")?; self.format_label(&annotation.label, f)?; @@ -343,7 +326,6 @@ impl<'a> DisplayList<'a> { let indent_char = match annotation_part { DisplayAnnotationPart::Standalone => ' ', DisplayAnnotationPart::LabelContinuation => ' ', - DisplayAnnotationPart::Consequitive => ' ', DisplayAnnotationPart::MultilineStart => '_', DisplayAnnotationPart::MultilineEnd => '_', }; @@ -358,31 +340,24 @@ impl<'a> DisplayList<'a> { let color = self.get_annotation_style(annotation_type); let indent_length = match annotation_part { DisplayAnnotationPart::LabelContinuation => range.1, - DisplayAnnotationPart::Consequitive => range.1, _ => range.0, }; - color.paint_fn( - Box::new(|f| { - format_repeat_char(indent_char, indent_length + 1, f)?; - format_repeat_char(mark, range.1 - indent_length, f) - }), - f, - )?; + write!(f, "{}", color.render())?; + format_repeat_char(indent_char, indent_length + 1, f)?; + format_repeat_char(mark, range.1 - indent_length, f)?; + write!(f, "{}", color.render_reset())?; if !is_annotation_empty(annotation) { f.write_char(' ')?; - color.paint_fn( - Box::new(|f| { - self.format_annotation( - annotation, - annotation_part == &DisplayAnnotationPart::LabelContinuation, - true, - f, - ) - }), + write!(f, "{}", color.render())?; + self.format_annotation( + annotation, + annotation_part == &DisplayAnnotationPart::LabelContinuation, + true, f, )?; + write!(f, "{}", color.render_reset())?; } Ok(()) @@ -407,11 +382,17 @@ impl<'a> DisplayList<'a> { DisplayHeaderType::Initial => "-->", DisplayHeaderType::Continuation => ":::", }; - let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); + let lineno_color = self.stylesheet.line_no(); if let Some((col, row)) = pos { format_repeat_char(' ', lineno_width, f)?; - lineno_color.paint(header_sigil, f)?; + write!( + f, + "{}{}{}", + lineno_color.render(), + header_sigil, + lineno_color.render_reset() + )?; f.write_char(' ')?; path.fmt(f)?; f.write_char(':')?; @@ -420,7 +401,13 @@ impl<'a> DisplayList<'a> { row.fmt(f) } else { format_repeat_char(' ', lineno_width, f)?; - lineno_color.paint(header_sigil, f)?; + write!( + f, + "{}{}{}", + lineno_color.render(), + header_sigil, + lineno_color.render_reset() + )?; f.write_char(' ')?; path.fmt(f) } @@ -434,10 +421,15 @@ impl<'a> DisplayList<'a> { if *continuation { format_repeat_char(' ', lineno_width + 3, f)?; } else { - let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); + let lineno_color = self.stylesheet.line_no(); format_repeat_char(' ', lineno_width, f)?; f.write_char(' ')?; - lineno_color.paint("=", f)?; + write!( + f, + "{}={}", + lineno_color.render(), + lineno_color.render_reset() + )?; f.write_char(' ')?; } } @@ -460,26 +452,20 @@ impl<'a> DisplayList<'a> { inline_marks, line, } => { - let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); + let lineno_color = self.stylesheet.line_no(); if self.anonymized_line_numbers && lineno.is_some() { - lineno_color.paint_fn( - Box::new(|f| { - f.write_str(Self::ANONYMIZED_LINE_NUM)?; - f.write_str(" |") - }), - f, - )?; + write!(f, "{}", lineno_color.render())?; + f.write_str(Self::ANONYMIZED_LINE_NUM)?; + f.write_str(" |")?; + write!(f, "{}", lineno_color.render_reset())?; } else { - lineno_color.paint_fn( - Box::new(|f| { - match lineno { - Some(n) => write!(f, "{:>width$}", n, width = lineno_width), - None => format_repeat_char(' ', lineno_width, f), - }?; - f.write_str(" |") - }), - f, - )?; + write!(f, "{}", lineno_color.render())?; + match lineno { + Some(n) => write!(f, "{:>width$}", n, width = lineno_width), + None => format_repeat_char(' ', lineno_width, f), + }?; + f.write_str(" |")?; + write!(f, "{}", lineno_color.render_reset())?; } if *line != DisplaySourceLine::Empty { if !inline_marks.is_empty() || 0 < inline_marks_width { @@ -513,141 +499,18 @@ impl<'a> DisplayList<'a> { ) -> fmt::Result { format_repeat_char(' ', inline_marks_width - inline_marks.len(), f)?; for mark in inline_marks { - self.get_annotation_style(&mark.annotation_type).paint_fn( - Box::new(|f| { - f.write_char(match mark.mark_type { - DisplayMarkType::AnnotationThrough => '|', - DisplayMarkType::AnnotationStart => '/', - }) - }), - f, - )?; + let annotation_style = self.get_annotation_style(&mark.annotation_type); + write!(f, "{}", annotation_style.render())?; + f.write_char(match mark.mark_type { + DisplayMarkType::AnnotationThrough => '|', + DisplayMarkType::AnnotationStart => '/', + })?; + write!(f, "{}", annotation_style.render_reset())?; } Ok(()) } } -#[derive(Debug, Default, Copy, Clone)] -pub struct FormatOptions { - pub color: bool, - pub anonymized_line_numbers: bool, - pub margin: Option, -} - -#[derive(Clone, Copy, Debug)] -pub struct Margin { - /// The available whitespace in the left that can be consumed when centering. - whitespace_left: usize, - /// The column of the beginning of left-most span. - span_left: usize, - /// The column of the end of right-most span. - span_right: usize, - /// The beginning of the line to be displayed. - computed_left: usize, - /// The end of the line to be displayed. - computed_right: usize, - /// The current width of the terminal. 140 by default and in tests. - column_width: usize, - /// The end column of a span label, including the span. Doesn't account for labels not in the - /// same line as the span. - label_right: usize, -} - -impl Margin { - pub fn new( - whitespace_left: usize, - span_left: usize, - span_right: usize, - label_right: usize, - column_width: usize, - max_line_len: usize, - ) -> Self { - // The 6 is padding to give a bit of room for `...` when displaying: - // ``` - // error: message - // --> file.rs:16:58 - // | - // 16 | ... fn foo(self) -> Self::Bar { - // | ^^^^^^^^^ - // ``` - - let mut m = Margin { - whitespace_left: whitespace_left.saturating_sub(6), - span_left: span_left.saturating_sub(6), - span_right: span_right + 6, - computed_left: 0, - computed_right: 0, - column_width, - label_right: label_right + 6, - }; - m.compute(max_line_len); - m - } - - pub(crate) fn was_cut_left(&self) -> bool { - self.computed_left > 0 - } - - pub(crate) fn was_cut_right(&self, line_len: usize) -> bool { - let right = - if self.computed_right == self.span_right || self.computed_right == self.label_right { - // Account for the "..." padding given above. Otherwise we end up with code lines that - // do fit but end in "..." as if they were trimmed. - self.computed_right - 6 - } else { - self.computed_right - }; - right < line_len && self.computed_left + self.column_width < line_len - } - - fn compute(&mut self, max_line_len: usize) { - // When there's a lot of whitespace (>20), we want to trim it as it is useless. - self.computed_left = if self.whitespace_left > 20 { - self.whitespace_left - 16 // We want some padding. - } else { - 0 - }; - // We want to show as much as possible, max_line_len is the right-most boundary for the - // relevant code. - self.computed_right = max(max_line_len, self.computed_left); - - if self.computed_right - self.computed_left > self.column_width { - // Trimming only whitespace isn't enough, let's get craftier. - if self.label_right - self.whitespace_left <= self.column_width { - // Attempt to fit the code window only trimming whitespace. - self.computed_left = self.whitespace_left; - self.computed_right = self.computed_left + self.column_width; - } else if self.label_right - self.span_left <= self.column_width { - // Attempt to fit the code window considering only the spans and labels. - let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2; - self.computed_left = self.span_left.saturating_sub(padding_left); - self.computed_right = self.computed_left + self.column_width; - } else if self.span_right - self.span_left <= self.column_width { - // Attempt to fit the code window considering the spans and labels plus padding. - let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2; - self.computed_left = self.span_left.saturating_sub(padding_left); - self.computed_right = self.computed_left + self.column_width; - } else { - // Mostly give up but still don't show the full line. - self.computed_left = self.span_left; - self.computed_right = self.span_right; - } - } - } - - pub(crate) fn left(&self, line_len: usize) -> usize { - min(self.computed_left, line_len) - } - - pub(crate) fn right(&self, line_len: usize) -> usize { - if line_len.saturating_sub(self.computed_left) <= self.column_width { - line_len - } else { - min(line_len, self.computed_right) - } - } -} - /// Inline annotation which can be used in either Raw or Source line. #[derive(Debug, PartialEq)] pub struct Annotation<'a> { @@ -744,8 +607,6 @@ pub enum DisplayAnnotationPart { Standalone, /// A continuation of a multi-line label of an annotation. LabelContinuation, - /// A consequitive annotation in case multiple annotations annotate a single line. - Consequitive, /// A line starting a multiline annotation. MultilineStart, /// A line ending a multiline annotation. @@ -1346,3 +1207,878 @@ fn is_annotation_empty(annotation: &Annotation<'_>) -> bool { .iter() .all(|fragment| fragment.content.is_empty()) } + +#[cfg(test)] +mod tests { + use super::*; + + const STYLESHEET: Stylesheet = Stylesheet::plain(); + + fn from_display_lines(lines: Vec>) -> DisplayList<'_> { + DisplayList { + body: lines, + stylesheet: &STYLESHEET, + anonymized_line_numbers: false, + margin: None, + } + } + + #[test] + fn test_format_title() { + let input = snippet::Snippet { + title: Some(snippet::Annotation { + id: Some("E0001"), + label: Some("This is a title"), + annotation_type: snippet::AnnotationType::Error, + }), + footer: vec![], + slices: vec![], + }; + let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Error, + id: Some("E0001"), + label: vec![DisplayTextFragment { + content: "This is a title", + style: DisplayTextStyle::Emphasis, + }], + }, + source_aligned: false, + continuation: false, + })]); + assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output); + } + + #[test] + fn test_format_slice() { + 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 { + title: None, + footer: vec![], + slices: vec![snippet::Slice { + source: &source, + line_start: 5402, + origin: None, + annotations: vec![], + fold: false, + }], + }; + let output = from_display_lines(vec![ + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Source { + lineno: Some(5402), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: line_1, + range: (0, line_1.len()), + }, + }, + DisplayLine::Source { + lineno: Some(5403), + inline_marks: vec![], + line: DisplaySourceLine::Content { + range: (line_1.len() + 1, source.len()), + text: line_2, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + ]); + assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output); + } + + #[test] + fn test_format_slices_continuation() { + let src_0 = "This is slice 1"; + 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 { + title: None, + footer: vec![], + slices: vec![ + snippet::Slice { + source: src_0, + line_start: 5402, + origin: Some("file1.rs"), + annotations: vec![], + fold: false, + }, + snippet::Slice { + source: src_1, + line_start: 2, + origin: Some("file2.rs"), + annotations: vec![], + fold: false, + }, + ], + }; + let output = from_display_lines(vec![ + DisplayLine::Raw(DisplayRawLine::Origin { + path: "file1.rs", + pos: None, + header_type: DisplayHeaderType::Initial, + }), + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Source { + lineno: Some(5402), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: src_0, + range: (0, src_0_len), + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Raw(DisplayRawLine::Origin { + path: "file2.rs", + pos: None, + header_type: DisplayHeaderType::Continuation, + }), + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Source { + lineno: Some(2), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: src_1, + range: (0, src_1_len), + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + ]); + assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output); + } + + #[test] + fn test_format_slice_annotation_standalone() { + let line_1 = "This is line 1"; + let line_2 = "This is line 2"; + let source = [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, + line_start: 5402, + origin: None, + annotations: vec![snippet::SourceAnnotation { + range, + label: "Test annotation", + annotation_type: snippet::AnnotationType::Info, + }], + fold: false, + }], + }; + let output = from_display_lines(vec![ + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Source { + lineno: Some(5402), + inline_marks: vec![], + line: DisplaySourceLine::Content { + range: (0, line_1.len()), + text: line_1, + }, + }, + DisplayLine::Source { + lineno: Some(5403), + inline_marks: vec![], + line: DisplaySourceLine::Content { + range: (line_1.len() + 1, source.len()), + text: line_2, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Info, + id: None, + label: vec![DisplayTextFragment { + content: "Test annotation", + style: DisplayTextStyle::Regular, + }], + }, + range: (range.0 - (line_1.len() + 1), range.1 - (line_1.len() + 1)), + annotation_type: DisplayAnnotationType::Info, + annotation_part: DisplayAnnotationPart::Standalone, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + ]); + assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output); + } + + #[test] + fn test_format_label() { + let input = snippet::Snippet { + title: None, + footer: vec![snippet::Annotation { + id: None, + label: Some("This __is__ a title"), + annotation_type: snippet::AnnotationType::Error, + }], + slices: vec![], + }; + let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Error, + id: None, + label: vec![DisplayTextFragment { + content: "This __is__ a title", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: true, + continuation: false, + })]); + assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output); + } + + #[test] + #[should_panic] + fn test_i26() { + let source = "short"; + let label = "label"; + let input = snippet::Snippet { + title: None, + footer: vec![], + slices: vec![snippet::Slice { + annotations: vec![snippet::SourceAnnotation { + range: (0, source.len() + 1), + label, + annotation_type: snippet::AnnotationType::Error, + }], + source, + line_start: 0, + origin: None, + fold: false, + }], + }; + let _ = DisplayList::new(input, &STYLESHEET, false, None); + } + + #[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, + }], + }; + + let expected = from_display_lines(vec![ + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Error, + id: None, + label: vec![DisplayTextFragment { + content: "oops", + style: DisplayTextStyle::Emphasis, + }], + }, + source_aligned: false, + continuation: false, + }), + DisplayLine::Raw(DisplayRawLine::Origin { + path: "", + pos: Some((2, 8)), + header_type: DisplayHeaderType::Initial, + }), + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Source { + lineno: Some(1), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "First line", + range: (0, 10), + }, + }, + DisplayLine::Source { + lineno: Some(2), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "Second oops line", + range: (12, 28), + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::None, + id: None, + label: vec![DisplayTextFragment { + content: "oops", + style: DisplayTextStyle::Regular, + }], + }, + range: (7, 11), + annotation_type: DisplayAnnotationType::Error, + annotation_part: DisplayAnnotationPart::Standalone, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + ]); + assert_eq!( + DisplayList::new(snippets, &STYLESHEET, false, None), + expected + ); + } + + #[test] + fn test_source_empty() { + let dl = from_display_lines(vec![DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }]); + + assert_eq!(dl.to_string(), " |"); + } + + #[test] + fn test_source_content() { + let dl = from_display_lines(vec![ + DisplayLine::Source { + lineno: Some(56), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "This is an example", + range: (0, 19), + }, + }, + DisplayLine::Source { + lineno: Some(57), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "of content lines", + range: (0, 19), + }, + }, + ]); + + assert_eq!( + dl.to_string(), + "56 | This is an example\n57 | of content lines" + ); + } + + #[test] + fn test_source_annotation_standalone_singleline() { + let dl = from_display_lines(vec![DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + range: (0, 5), + annotation: Annotation { + annotation_type: DisplayAnnotationType::None, + id: None, + label: vec![DisplayTextFragment { + content: "Example string", + style: DisplayTextStyle::Regular, + }], + }, + annotation_type: DisplayAnnotationType::Error, + annotation_part: DisplayAnnotationPart::Standalone, + }, + }]); + + assert_eq!(dl.to_string(), " | ^^^^^ Example string"); + } + + #[test] + fn test_source_annotation_standalone_multiline() { + let dl = from_display_lines(vec![ + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + range: (0, 5), + annotation: Annotation { + annotation_type: DisplayAnnotationType::Help, + id: None, + label: vec![DisplayTextFragment { + content: "Example string", + style: DisplayTextStyle::Regular, + }], + }, + annotation_type: DisplayAnnotationType::Warning, + annotation_part: DisplayAnnotationPart::Standalone, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + range: (0, 5), + annotation: Annotation { + annotation_type: DisplayAnnotationType::Help, + id: None, + label: vec![DisplayTextFragment { + content: "Second line", + style: DisplayTextStyle::Regular, + }], + }, + annotation_type: DisplayAnnotationType::Warning, + annotation_part: DisplayAnnotationPart::LabelContinuation, + }, + }, + ]); + + assert_eq!( + dl.to_string(), + " | ----- help: Example string\n | Second line" + ); + } + + #[test] + fn test_source_annotation_standalone_multi_annotation() { + let dl = from_display_lines(vec![ + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + range: (0, 5), + annotation: Annotation { + annotation_type: DisplayAnnotationType::Info, + id: None, + label: vec![DisplayTextFragment { + content: "Example string", + style: DisplayTextStyle::Regular, + }], + }, + annotation_type: DisplayAnnotationType::Note, + annotation_part: DisplayAnnotationPart::Standalone, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + range: (0, 5), + annotation: Annotation { + annotation_type: DisplayAnnotationType::Info, + id: None, + label: vec![DisplayTextFragment { + content: "Second line", + style: DisplayTextStyle::Regular, + }], + }, + annotation_type: DisplayAnnotationType::Note, + annotation_part: DisplayAnnotationPart::LabelContinuation, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + range: (0, 5), + annotation: Annotation { + annotation_type: DisplayAnnotationType::Warning, + id: None, + label: vec![DisplayTextFragment { + content: "Second line of the warning", + style: DisplayTextStyle::Regular, + }], + }, + annotation_type: DisplayAnnotationType::Note, + annotation_part: DisplayAnnotationPart::LabelContinuation, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + range: (0, 5), + annotation: Annotation { + annotation_type: DisplayAnnotationType::Info, + id: None, + label: vec![DisplayTextFragment { + content: "This is an info", + style: DisplayTextStyle::Regular, + }], + }, + annotation_type: DisplayAnnotationType::Info, + annotation_part: DisplayAnnotationPart::Standalone, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + range: (0, 5), + annotation: Annotation { + annotation_type: DisplayAnnotationType::Help, + id: None, + label: vec![DisplayTextFragment { + content: "This is help", + style: DisplayTextStyle::Regular, + }], + }, + annotation_type: DisplayAnnotationType::Help, + annotation_part: DisplayAnnotationPart::Standalone, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + range: (0, 0), + annotation: Annotation { + annotation_type: DisplayAnnotationType::None, + id: None, + label: vec![DisplayTextFragment { + content: "This is an annotation of type none", + style: DisplayTextStyle::Regular, + }], + }, + annotation_type: DisplayAnnotationType::None, + annotation_part: DisplayAnnotationPart::Standalone, + }, + }, + ]); + + assert_eq!(dl.to_string(), " | ----- info: Example string\n | Second line\n | Second line of the warning\n | ----- info: This is an info\n | ----- help: This is help\n | This is an annotation of type none"); + } + + #[test] + fn test_fold_line() { + let dl = from_display_lines(vec![ + DisplayLine::Source { + lineno: Some(5), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "This is line 5", + range: (0, 19), + }, + }, + DisplayLine::Fold { + inline_marks: vec![], + }, + DisplayLine::Source { + lineno: Some(10021), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "... and now we're at line 10021", + range: (0, 19), + }, + }, + ]); + + assert_eq!( + dl.to_string(), + " 5 | This is line 5\n...\n10021 | ... and now we're at line 10021" + ); + } + + #[test] + fn test_raw_origin_initial_nopos() { + let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin { + path: "src/test.rs", + pos: None, + header_type: DisplayHeaderType::Initial, + })]); + + assert_eq!(dl.to_string(), "--> src/test.rs"); + } + + #[test] + fn test_raw_origin_initial_pos() { + let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin { + path: "src/test.rs", + pos: Some((23, 15)), + header_type: DisplayHeaderType::Initial, + })]); + + assert_eq!(dl.to_string(), "--> src/test.rs:23:15"); + } + + #[test] + fn test_raw_origin_continuation() { + let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin { + path: "src/test.rs", + pos: Some((23, 15)), + header_type: DisplayHeaderType::Continuation, + })]); + + assert_eq!(dl.to_string(), "::: src/test.rs:23:15"); + } + + #[test] + fn test_raw_annotation_unaligned() { + let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Error, + id: Some("E0001"), + label: vec![DisplayTextFragment { + content: "This is an error", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: false, + continuation: false, + })]); + + assert_eq!(dl.to_string(), "error[E0001]: This is an error"); + } + + #[test] + fn test_raw_annotation_unaligned_multiline() { + let dl = from_display_lines(vec![ + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Warning, + id: Some("E0001"), + label: vec![DisplayTextFragment { + content: "This is an error", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: false, + continuation: false, + }), + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Warning, + id: Some("E0001"), + label: vec![DisplayTextFragment { + content: "Second line of the error", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: false, + continuation: true, + }), + ]); + + assert_eq!( + dl.to_string(), + "warning[E0001]: This is an error\n Second line of the error" + ); + } + + #[test] + fn test_raw_annotation_aligned() { + let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Error, + id: Some("E0001"), + label: vec![DisplayTextFragment { + content: "This is an error", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: true, + continuation: false, + })]); + + assert_eq!(dl.to_string(), " = error[E0001]: This is an error"); + } + + #[test] + fn test_raw_annotation_aligned_multiline() { + let dl = from_display_lines(vec![ + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Warning, + id: Some("E0001"), + label: vec![DisplayTextFragment { + content: "This is an error", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: true, + continuation: false, + }), + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Warning, + id: Some("E0001"), + label: vec![DisplayTextFragment { + content: "Second line of the error", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: true, + continuation: true, + }), + ]); + + assert_eq!( + dl.to_string(), + " = warning[E0001]: This is an error\n Second line of the error" + ); + } + + #[test] + fn test_different_annotation_types() { + let dl = from_display_lines(vec![ + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Note, + id: None, + label: vec![DisplayTextFragment { + content: "This is a note", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: false, + continuation: false, + }), + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::None, + id: None, + label: vec![DisplayTextFragment { + content: "This is just a string", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: false, + continuation: false, + }), + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::None, + id: None, + label: vec![DisplayTextFragment { + content: "Second line of none type annotation", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: false, + continuation: true, + }), + ]); + + assert_eq!( + dl.to_string(), + "note: This is a note\nThis is just a string\n Second line of none type annotation", + ); + } + + #[test] + fn test_inline_marks_empty_line() { + let dl = from_display_lines(vec![DisplayLine::Source { + lineno: None, + inline_marks: vec![DisplayMark { + mark_type: DisplayMarkType::AnnotationThrough, + annotation_type: DisplayAnnotationType::Error, + }], + line: DisplaySourceLine::Empty, + }]); + + assert_eq!(dl.to_string(), " | |",); + } + + #[test] + fn test_anon_lines() { + let mut dl = from_display_lines(vec![ + DisplayLine::Source { + lineno: Some(56), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "This is an example", + range: (0, 19), + }, + }, + DisplayLine::Source { + lineno: Some(57), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "of content lines", + range: (0, 19), + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "abc", + range: (0, 19), + }, + }, + ]); + + dl.anonymized_line_numbers = true; + assert_eq!( + dl.to_string(), + "LL | This is an example\nLL | of content lines\n |\n | abc" + ); + } + + #[test] + fn test_raw_origin_initial_pos_anon_lines() { + let mut dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin { + path: "src/test.rs", + pos: Some((23, 15)), + header_type: DisplayHeaderType::Initial, + })]); + + // Using anonymized_line_numbers should not affect the initial position + dl.anonymized_line_numbers = true; + assert_eq!(dl.to_string(), "--> src/test.rs:23:15"); + } +} diff --git a/src/renderer/margin.rs b/src/renderer/margin.rs new file mode 100644 index 00000000..361f5f36 --- /dev/null +++ b/src/renderer/margin.rs @@ -0,0 +1,119 @@ +use std::cmp::{max, min}; + +const ELLIPSIS_PASSING: usize = 6; +const LONG_WHITESPACE: usize = 20; +const LONG_WHITESPACE_PADDING: usize = 4; + +#[derive(Clone, Copy, Debug)] +pub struct Margin { + /// The available whitespace in the left that can be consumed when centering. + whitespace_left: usize, + /// The column of the beginning of left-most span. + span_left: usize, + /// The column of the end of right-most span. + span_right: usize, + /// The beginning of the line to be displayed. + computed_left: usize, + /// The end of the line to be displayed. + computed_right: usize, + /// The current width of the terminal. 140 by default and in tests. + column_width: usize, + /// The end column of a span label, including the span. Doesn't account for labels not in the + /// same line as the span. + label_right: usize, +} + +impl Margin { + pub fn new( + whitespace_left: usize, + span_left: usize, + span_right: usize, + label_right: usize, + column_width: usize, + max_line_len: usize, + ) -> Self { + // The 6 is padding to give a bit of room for `...` when displaying: + // ``` + // error: message + // --> file.rs:16:58 + // | + // 16 | ... fn foo(self) -> Self::Bar { + // | ^^^^^^^^^ + // ``` + + let mut m = Margin { + whitespace_left: whitespace_left.saturating_sub(ELLIPSIS_PASSING), + span_left: span_left.saturating_sub(ELLIPSIS_PASSING), + span_right: span_right + ELLIPSIS_PASSING, + computed_left: 0, + computed_right: 0, + column_width, + label_right: label_right + ELLIPSIS_PASSING, + }; + m.compute(max_line_len); + m + } + + pub(crate) fn was_cut_left(&self) -> bool { + self.computed_left > 0 + } + + pub(crate) fn was_cut_right(&self, line_len: usize) -> bool { + let right = + if self.computed_right == self.span_right || self.computed_right == self.label_right { + // Account for the "..." padding given above. Otherwise we end up with code lines that + // do fit but end in "..." as if they were trimmed. + self.computed_right - ELLIPSIS_PASSING + } else { + self.computed_right + }; + right < line_len && self.computed_left + self.column_width < line_len + } + + fn compute(&mut self, max_line_len: usize) { + // When there's a lot of whitespace (>20), we want to trim it as it is useless. + self.computed_left = if self.whitespace_left > LONG_WHITESPACE { + self.whitespace_left - (LONG_WHITESPACE - LONG_WHITESPACE_PADDING) // We want some padding. + } else { + 0 + }; + // We want to show as much as possible, max_line_len is the right-most boundary for the + // relevant code. + self.computed_right = max(max_line_len, self.computed_left); + + if self.computed_right - self.computed_left > self.column_width { + // Trimming only whitespace isn't enough, let's get craftier. + if self.label_right - self.whitespace_left <= self.column_width { + // Attempt to fit the code window only trimming whitespace. + self.computed_left = self.whitespace_left; + self.computed_right = self.computed_left + self.column_width; + } else if self.label_right - self.span_left <= self.column_width { + // Attempt to fit the code window considering only the spans and labels. + let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2; + self.computed_left = self.span_left.saturating_sub(padding_left); + self.computed_right = self.computed_left + self.column_width; + } else if self.span_right - self.span_left <= self.column_width { + // Attempt to fit the code window considering the spans and labels plus padding. + let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2; + self.computed_left = self.span_left.saturating_sub(padding_left); + self.computed_right = self.computed_left + self.column_width; + } else { + // Mostly give up but still don't show the full line. + self.computed_left = self.span_left; + self.computed_right = self.span_right; + } + } + } + + pub(crate) fn left(&self, line_len: usize) -> usize { + min(self.computed_left, line_len) + } + + pub(crate) fn right(&self, line_len: usize) -> usize { + if line_len.saturating_sub(self.computed_left) <= self.column_width { + line_len + } else { + min(line_len, self.computed_right) + } + } +} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs new file mode 100644 index 00000000..0e19c08b --- /dev/null +++ b/src/renderer/mod.rs @@ -0,0 +1,178 @@ +//! The renderer for [`Snippet`]s +//! +//! # Example +//! ``` +//! use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet}; +//! let snippet = Snippet { +//! title: Some(Annotation { +//! label: Some("mismatched types"), +//! id: None, +//! annotation_type: AnnotationType::Error, +//! }), +//! footer: vec![], +//! slices: vec![ +//! Slice { +//! source: "Foo", +//! line_start: 51, +//! origin: Some("src/format.rs"), +//! fold: false, +//! annotations: vec![], +//! }, +//! Slice { +//! source: "Faa", +//! line_start: 129, +//! origin: Some("src/display.rs"), +//! fold: false, +//! annotations: vec![], +//! }, +//! ], +//! }; +//! +//! let renderer = Renderer::styled(); +//! println!("{}", renderer.render(snippet)); + +mod display_list; +mod margin; +pub(crate) mod stylesheet; + +use crate::snippet::Snippet; +pub use anstyle::*; +use display_list::DisplayList; +pub use margin::Margin; +use std::fmt::Display; +use stylesheet::Stylesheet; + +/// A renderer for [`Snippet`]s +#[derive(Clone)] +pub struct Renderer { + anonymized_line_numbers: bool, + margin: Option, + stylesheet: Stylesheet, +} + +impl Renderer { + /// No terminal styling + pub const fn plain() -> Self { + Self { + anonymized_line_numbers: false, + margin: None, + stylesheet: Stylesheet::plain(), + } + } + + /// Default terminal styling + pub const fn styled() -> Self { + Self { + stylesheet: Stylesheet { + error: AnsiColor::BrightRed.on_default().effects(Effects::BOLD), + warning: AnsiColor::BrightYellow.on_default().effects(Effects::BOLD), + info: AnsiColor::BrightBlue.on_default().effects(Effects::BOLD), + note: Style::new().effects(Effects::BOLD), + help: AnsiColor::BrightCyan.on_default().effects(Effects::BOLD), + line_no: AnsiColor::BrightBlue.on_default().effects(Effects::BOLD), + emphasis: Style::new().effects(Effects::BOLD), + none: Style::new(), + }, + ..Self::plain() + } + } + + /// Anonymize line numbers + /// + /// This enables (or disables) line number anonymization. When enabled, line numbers are replaced + /// with `LL`. + /// + /// # Example + /// + /// ```text + /// --> $DIR/whitespace-trimming.rs:4:193 + /// | + /// LL | ... let _: () = 42; + /// | ^^ expected (), found integer + /// | + /// ``` + pub const fn anonymized_line_numbers(mut self, anonymized_line_numbers: bool) -> Self { + self.anonymized_line_numbers = anonymized_line_numbers; + self + } + + /// Set the margin for the output + /// + /// This controls the various margins of the output. + /// + /// # Example + /// + /// ```text + /// error: expected type, found `22` + /// --> examples/footer.rs:29:25 + /// | + /// 26 | ... annotations: vec![SourceAnnotation { + /// | ---------------- info: while parsing this struct + /// ... + /// 29 | ... range: <22, 25>, + /// | ^^ + /// | + /// ``` + pub const fn margin(mut self, margin: Option) -> Self { + self.margin = margin; + self + } + + /// Set the output style for `error` + pub const fn error(mut self, style: Style) -> Self { + self.stylesheet.error = style; + self + } + + /// Set the output style for `warning` + pub const fn warning(mut self, style: Style) -> Self { + self.stylesheet.warning = style; + self + } + + /// Set the output style for `info` + pub const fn info(mut self, style: Style) -> Self { + self.stylesheet.info = style; + self + } + + /// Set the output style for `note` + pub const fn note(mut self, style: Style) -> Self { + self.stylesheet.note = style; + self + } + + /// Set the output style for `help` + pub const fn help(mut self, style: Style) -> Self { + self.stylesheet.help = style; + self + } + + /// Set the output style for line numbers + pub const fn line_no(mut self, style: Style) -> Self { + self.stylesheet.line_no = style; + self + } + + /// Set the output style for emphasis + pub const fn emphasis(mut self, style: Style) -> Self { + self.stylesheet.emphasis = style; + self + } + + /// Set the output style for none + pub const fn none(mut self, style: Style) -> Self { + self.stylesheet.none = style; + self + } + + /// Render a snippet into a `Display`able object + pub fn render<'a>(&'a self, snippet: Snippet<'a>) -> impl Display + 'a { + DisplayList::new( + snippet, + &self.stylesheet, + self.anonymized_line_numbers, + self.margin, + ) + } +} diff --git a/src/renderer/stylesheet.rs b/src/renderer/stylesheet.rs new file mode 100644 index 00000000..ee1ab937 --- /dev/null +++ b/src/renderer/stylesheet.rs @@ -0,0 +1,68 @@ +use anstyle::Style; + +#[derive(Clone, Copy, Debug)] +pub(crate) struct Stylesheet { + pub(crate) error: Style, + pub(crate) warning: Style, + pub(crate) info: Style, + pub(crate) note: Style, + pub(crate) help: Style, + pub(crate) line_no: Style, + pub(crate) emphasis: Style, + pub(crate) none: Style, +} + +impl Default for Stylesheet { + fn default() -> Self { + Self::plain() + } +} + +impl Stylesheet { + pub(crate) const fn plain() -> Self { + Self { + error: Style::new(), + warning: Style::new(), + info: Style::new(), + note: Style::new(), + help: Style::new(), + line_no: Style::new(), + emphasis: Style::new(), + none: Style::new(), + } + } +} + +impl Stylesheet { + pub(crate) fn error(&self) -> &Style { + &self.error + } + + pub(crate) fn warning(&self) -> &Style { + &self.warning + } + + pub(crate) fn info(&self) -> &Style { + &self.info + } + + pub(crate) fn note(&self) -> &Style { + &self.note + } + + pub(crate) fn help(&self) -> &Style { + &self.help + } + + pub(crate) fn line_no(&self) -> &Style { + &self.line_no + } + + pub(crate) fn emphasis(&self) -> &Style { + &self.emphasis + } + + pub(crate) fn none(&self) -> &Style { + &self.none + } +} diff --git a/src/snippet.rs b/src/snippet.rs index bc7ba009..02e70cc1 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -3,7 +3,7 @@ //! Example: //! //! ``` -//! use annotate_snippets::snippet::*; +//! use annotate_snippets::*; //! //! Snippet { //! title: Some(Annotation { @@ -28,10 +28,8 @@ //! annotations: vec![], //! }, //! ], -//! opt: Default::default(), //! }; //! ``` -use crate::display_list::FormatOptions; /// Primary structure provided for formatting #[derive(Debug, Default)] @@ -39,7 +37,6 @@ pub struct Snippet<'a> { pub title: Option>, pub footer: Vec>, pub slices: Vec>, - pub opt: FormatOptions, } /// Structure containing the slice of text to be annotated and diff --git a/src/stylesheets/color.rs b/src/stylesheets/color.rs deleted file mode 100644 index 024dd06f..00000000 --- a/src/stylesheets/color.rs +++ /dev/null @@ -1,50 +0,0 @@ -use std::fmt::{self, Display}; - -use yansi_term::{Color::Fixed, Style as AnsiTermStyle}; - -use crate::formatter::style::{Style, StyleClass, Stylesheet}; - -struct AnsiTermStyleWrapper { - style: AnsiTermStyle, -} - -impl Style for AnsiTermStyleWrapper { - fn paint(&self, text: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.style.paint(text).fmt(f) - } - - fn paint_fn<'a>( - &self, - c: Box) -> fmt::Result + 'a>, - f: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - self.style.paint_fn(c).fmt(f) - } - - fn bold(&self) -> Box { - Box::new(AnsiTermStyleWrapper { style: self.style }) - } -} - -pub struct AnsiTermStylesheet; - -impl Stylesheet for AnsiTermStylesheet { - fn get_style(&self, class: StyleClass) -> Box { - let ansi_term_style = match class { - StyleClass::Error => Fixed(9).bold(), - StyleClass::Warning => Fixed(11).bold(), - StyleClass::Info => Fixed(12).bold(), - StyleClass::Note => AnsiTermStyle::new().bold(), - StyleClass::Help => Fixed(14).bold(), - - StyleClass::LineNo => Fixed(12).bold(), - - StyleClass::Emphasis => AnsiTermStyle::new().bold(), - - StyleClass::None => AnsiTermStyle::new(), - }; - Box::new(AnsiTermStyleWrapper { - style: ansi_term_style, - }) - } -} diff --git a/src/stylesheets/mod.rs b/src/stylesheets/mod.rs deleted file mode 100644 index 4648852a..00000000 --- a/src/stylesheets/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! List of stylesheets -//! -//! The list depends on what optional dependencies the crate has been -//! compiled with. -//! -//! By default the `no_color` is available. If the crate gets compiled -//! with `ansi_term`, the `color` stylesheet is added. - -#[cfg(feature = "color")] -pub mod color; -pub mod no_color; diff --git a/src/stylesheets/no_color.rs b/src/stylesheets/no_color.rs deleted file mode 100644 index 21cb2695..00000000 --- a/src/stylesheets/no_color.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::fmt; - -use crate::formatter::style::{Style, StyleClass, Stylesheet}; - -pub struct NoOpStyle {} - -impl Style for NoOpStyle { - fn paint(&self, text: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(text) - } - - fn paint_fn<'a>( - &self, - c: Box) -> fmt::Result + 'a>, - f: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - c(f) - } - - fn bold(&self) -> Box { - Box::new(NoOpStyle {}) - } -} - -pub struct NoColorStylesheet; - -impl Stylesheet for NoColorStylesheet { - fn get_style(&self, _class: StyleClass) -> Box { - Box::new(NoOpStyle {}) - } -} diff --git a/tests/snippet/mod.rs b/tests/deserialize/mod.rs similarity index 87% rename from tests/snippet/mod.rs rename to tests/deserialize/mod.rs index 92d272de..1763005a 100644 --- a/tests/snippet/mod.rs +++ b/tests/deserialize/mod.rs @@ -1,10 +1,17 @@ use serde::{Deserialize, Deserializer, Serialize}; use annotate_snippets::{ - display_list::{FormatOptions, Margin}, - snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, + renderer::Margin, Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation, }; +#[derive(Deserialize)] +pub struct Fixture<'a> { + #[serde(default)] + pub renderer: RendererDef, + #[serde(borrow)] + pub snippet: SnippetDef<'a>, +} + #[derive(Deserialize)] pub struct SnippetDef<'a> { #[serde(deserialize_with = "deserialize_annotation")] @@ -15,9 +22,6 @@ pub struct SnippetDef<'a> { #[serde(default)] #[serde(borrow)] pub footer: Vec>, - #[serde(deserialize_with = "deserialize_opt")] - #[serde(default)] - pub opt: FormatOptions, #[serde(deserialize_with = "deserialize_slices")] #[serde(borrow)] pub slices: Vec>, @@ -28,76 +32,16 @@ impl<'a> From> for Snippet<'a> { let SnippetDef { title, footer, - opt, slices, } = val; Snippet { title, footer, slices, - opt, } } } -fn deserialize_opt<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - struct Wrapper(#[serde(with = "FormatOptionsDef")] FormatOptions); - - Wrapper::deserialize(deserializer).map(|w| w.0) -} - -#[derive(Deserialize)] -#[serde(remote = "FormatOptions")] -pub struct FormatOptionsDef { - #[serde(default)] - pub color: bool, - #[serde(default)] - pub anonymized_line_numbers: bool, - #[serde(deserialize_with = "deserialize_margin")] - #[serde(default)] - pub margin: Option, -} - -fn deserialize_margin<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - struct Wrapper { - whitespace_left: usize, - span_left: usize, - span_right: usize, - label_right: usize, - column_width: usize, - max_line_len: usize, - } - - Option::::deserialize(deserializer).map(|opt_wrapped: Option| { - opt_wrapped.map(|wrapped: Wrapper| { - let Wrapper { - whitespace_left, - span_left, - span_right, - label_right, - column_width, - max_line_len, - } = wrapped; - Margin::new( - whitespace_left, - span_left, - span_right, - label_right, - column_width, - max_line_len, - ) - }) - }) -} - fn deserialize_slices<'de, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, @@ -206,3 +150,60 @@ enum AnnotationTypeDef { Note, Help, } + +#[derive(Default, Deserialize)] +pub struct RendererDef { + #[serde(default)] + anonymized_line_numbers: bool, + #[serde(deserialize_with = "deserialize_margin")] + #[serde(default)] + margin: Option, +} + +impl From for Renderer { + fn from(val: RendererDef) -> Self { + let RendererDef { + anonymized_line_numbers, + margin, + } = val; + Renderer::plain() + .anonymized_line_numbers(anonymized_line_numbers) + .margin(margin) + } +} + +fn deserialize_margin<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + struct Wrapper { + whitespace_left: usize, + span_left: usize, + span_right: usize, + label_right: usize, + column_width: usize, + max_line_len: usize, + } + + Option::::deserialize(deserializer).map(|opt_wrapped: Option| { + opt_wrapped.map(|wrapped: Wrapper| { + let Wrapper { + whitespace_left, + span_left, + span_right, + label_right, + column_width, + max_line_len, + } = wrapped; + Margin::new( + whitespace_left, + span_left, + span_right, + label_right, + column_width, + max_line_len, + ) + }) + }) +} diff --git a/tests/diff/mod.rs b/tests/diff/mod.rs index 576c6c4d..60ccae19 100644 --- a/tests/diff/mod.rs +++ b/tests/diff/mod.rs @@ -1,6 +1,7 @@ +use annotate_snippets::renderer::{AnsiColor, Color, Style}; use difference::{Changeset, Difference}; -use yansi_term::Color::{Black, Green, Red}; +const GREEN: Style = AnsiColor::Green.on_default(); pub fn get_diff(left: &str, right: &str) -> String { let mut output = String::new(); @@ -14,15 +15,28 @@ pub fn get_diff(left: &str, right: &str) -> String { Difference::Add(ref x) => { match diffs[i - 1] { Difference::Rem(ref y) => { - output += &format!("{}", Green.paint("+")); + output += &format!("{}+{}", GREEN.render(), GREEN.render_reset()); let Changeset { diffs, .. } = Changeset::new(y, x, " "); for c in diffs { match c { Difference::Same(ref z) => { - output += &format!("{} ", Green.paint(z.as_str())); + output += &format!( + "{}{}{} ", + GREEN.render(), + z.as_str(), + GREEN.render_reset() + ); } Difference::Add(ref z) => { - output += &format!("{} ", Black.on(Green).paint(z.as_str())); + let black_on_green = Style::new() + .bg_color(Some(Color::Ansi(AnsiColor::Green))) + .fg_color(Some(Color::Ansi(AnsiColor::Black))); + output += &format!( + "{}{}{} ", + black_on_green.render(), + z.as_str(), + black_on_green.render_reset() + ); } _ => (), } @@ -30,12 +44,18 @@ pub fn get_diff(left: &str, right: &str) -> String { output += "\n"; } _ => { - output += &format!("+{}\n", Green.paint(x.as_str())); + output += &format!( + "+{}{}{}\n", + GREEN.render(), + x.as_str(), + GREEN.render_reset() + ); } }; } Difference::Rem(ref x) => { - output += &format!("-{}\n", Red.paint(x.as_str())); + let red = AnsiColor::Red.on_default(); + output += &format!("-{}{}{}\n", red.render(), x.as_str(), red.render_reset()); } } } diff --git a/tests/dl_from_snippet.rs b/tests/dl_from_snippet.rs deleted file mode 100644 index 06f2ff76..00000000 --- a/tests/dl_from_snippet.rs +++ /dev/null @@ -1,397 +0,0 @@ -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"), - label: Some("This is a title"), - annotation_type: snippet::AnnotationType::Error, - }), - footer: vec![], - slices: vec![], - opt: Default::default(), - }; - let output = dl::DisplayList { - body: vec![dl::DisplayLine::Raw(dl::DisplayRawLine::Annotation { - annotation: dl::Annotation { - annotation_type: dl::DisplayAnnotationType::Error, - id: Some("E0001"), - label: vec![dl::DisplayTextFragment { - content: "This is a title", - style: dl::DisplayTextStyle::Emphasis, - }], - }, - source_aligned: false, - continuation: false, - })], - stylesheet: get_term_style(input.opt.color), - anonymized_line_numbers: input.opt.anonymized_line_numbers, - margin: None, - }; - assert_eq!(dl::DisplayList::from(input), output); -} - -#[test] -fn test_format_slice() { - 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 { - title: None, - footer: vec![], - slices: vec![snippet::Slice { - source: &source, - line_start: 5402, - origin: None, - annotations: vec![], - fold: false, - }], - opt: Default::default(), - }; - let output = dl::DisplayList { - body: vec![ - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Empty, - }, - dl::DisplayLine::Source { - lineno: Some(5402), - inline_marks: vec![], - line: dl::DisplaySourceLine::Content { - text: line_1, - range: (0, line_1.len()), - }, - }, - dl::DisplayLine::Source { - lineno: Some(5403), - inline_marks: vec![], - line: dl::DisplaySourceLine::Content { - range: (line_1.len() + 1, source.len()), - text: line_2, - }, - }, - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Empty, - }, - ], - stylesheet: get_term_style(input.opt.color), - anonymized_line_numbers: input.opt.anonymized_line_numbers, - margin: None, - }; - assert_eq!(dl::DisplayList::from(input), output); -} - -#[test] -fn test_format_slices_continuation() { - let src_0 = "This is slice 1"; - 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 { - title: None, - footer: vec![], - slices: vec![ - snippet::Slice { - source: src_0, - line_start: 5402, - origin: Some("file1.rs"), - annotations: vec![], - fold: false, - }, - snippet::Slice { - source: src_1, - line_start: 2, - origin: Some("file2.rs"), - annotations: vec![], - fold: false, - }, - ], - opt: Default::default(), - }; - let output = dl::DisplayList { - body: vec![ - dl::DisplayLine::Raw(dl::DisplayRawLine::Origin { - path: "file1.rs", - pos: None, - header_type: dl::DisplayHeaderType::Initial, - }), - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Empty, - }, - dl::DisplayLine::Source { - lineno: Some(5402), - inline_marks: vec![], - line: dl::DisplaySourceLine::Content { - text: src_0, - range: (0, src_0_len), - }, - }, - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Empty, - }, - dl::DisplayLine::Raw(dl::DisplayRawLine::Origin { - path: "file2.rs", - pos: None, - header_type: dl::DisplayHeaderType::Continuation, - }), - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Empty, - }, - dl::DisplayLine::Source { - lineno: Some(2), - inline_marks: vec![], - line: dl::DisplaySourceLine::Content { - text: src_1, - range: (0, src_1_len), - }, - }, - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Empty, - }, - ], - stylesheet: get_term_style(input.opt.color), - anonymized_line_numbers: input.opt.anonymized_line_numbers, - margin: None, - }; - assert_eq!(dl::DisplayList::from(input), output); -} - -#[test] -fn test_format_slice_annotation_standalone() { - let line_1 = "This is line 1"; - let line_2 = "This is line 2"; - let source = [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, - line_start: 5402, - origin: None, - annotations: vec![snippet::SourceAnnotation { - range, - label: "Test annotation", - annotation_type: snippet::AnnotationType::Info, - }], - fold: false, - }], - opt: Default::default(), - }; - let output = dl::DisplayList { - body: vec![ - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Empty, - }, - dl::DisplayLine::Source { - lineno: Some(5402), - inline_marks: vec![], - line: dl::DisplaySourceLine::Content { - range: (0, line_1.len()), - text: line_1, - }, - }, - dl::DisplayLine::Source { - lineno: Some(5403), - inline_marks: vec![], - line: dl::DisplaySourceLine::Content { - range: (line_1.len() + 1, source.len()), - text: line_2, - }, - }, - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Annotation { - annotation: dl::Annotation { - annotation_type: dl::DisplayAnnotationType::Info, - id: None, - label: vec![dl::DisplayTextFragment { - content: "Test annotation", - style: dl::DisplayTextStyle::Regular, - }], - }, - range: (range.0 - (line_1.len() + 1), range.1 - (line_1.len() + 1)), - annotation_type: dl::DisplayAnnotationType::Info, - annotation_part: dl::DisplayAnnotationPart::Standalone, - }, - }, - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Empty, - }, - ], - stylesheet: get_term_style(input.opt.color), - anonymized_line_numbers: input.opt.anonymized_line_numbers, - margin: None, - }; - assert_eq!(dl::DisplayList::from(input), output); -} - -#[test] -fn test_format_label() { - let input = snippet::Snippet { - title: None, - footer: vec![snippet::Annotation { - id: None, - label: Some("This __is__ a title"), - annotation_type: snippet::AnnotationType::Error, - }], - slices: vec![], - opt: Default::default(), - }; - let output = dl::DisplayList { - body: vec![dl::DisplayLine::Raw(dl::DisplayRawLine::Annotation { - annotation: dl::Annotation { - annotation_type: dl::DisplayAnnotationType::Error, - id: None, - label: vec![dl::DisplayTextFragment { - content: "This __is__ a title", - style: dl::DisplayTextStyle::Regular, - }], - }, - source_aligned: true, - continuation: false, - })], - stylesheet: get_term_style(input.opt.color), - anonymized_line_numbers: input.opt.anonymized_line_numbers, - margin: None, - }; - assert_eq!(dl::DisplayList::from(input), output); -} - -#[test] -#[should_panic] -fn test_i26() { - let source = "short"; - let label = "label"; - let input = snippet::Snippet { - title: None, - footer: vec![], - slices: vec![snippet::Slice { - annotations: vec![snippet::SourceAnnotation { - range: (0, source.len() + 1), - label, - annotation_type: snippet::AnnotationType::Error, - }], - source, - line_start: 0, - origin: None, - fold: false, - }], - opt: Default::default(), - }; - - 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, - margin: None, - }; - - assert_eq!(DisplayList::from(snippets), expected); -} diff --git a/tests/fixtures/no-color/issue_52.toml b/tests/fixtures/no-color/issue_52.toml index 8d54613f..d31e0cb2 100644 --- a/tests/fixtures/no-color/issue_52.toml +++ b/tests/fixtures/no-color/issue_52.toml @@ -1,7 +1,7 @@ -[title] +[snippet.title] annotation_type = "Error" -[[slices]] +[[snippet.slices]] source = """ @@ -10,7 +10,7 @@ invalid syntax line_start = 1 origin = "path/to/error.rs" fold = true -[[slices.annotations]] +[[snippet.slices.annotations]] label = "error here" annotation_type = "Warning" range = [2,16] diff --git a/tests/fixtures/no-color/issue_9.toml b/tests/fixtures/no-color/issue_9.toml index a30563b2..ee1fe27b 100644 --- a/tests/fixtures/no-color/issue_9.toml +++ b/tests/fixtures/no-color/issue_9.toml @@ -1,28 +1,28 @@ -[title] +[snippet.title] label = "expected one of `.`, `;`, `?`, or an operator, found `for`" annotation_type = "Error" -[[slices]] +[[snippet.slices]] source = "let x = vec![1];" line_start = 4 origin = "/code/rust/src/test/ui/annotate-snippet/suggestion.rs" -[[slices.annotations]] +[[snippet.slices.annotations]] label = "move occurs because `x` has type `std::vec::Vec`, which does not implement the `Copy` trait" annotation_type = "Warning" range = [4, 5] -[[slices]] +[[snippet.slices]] source = "let y = x;" line_start = 7 -[[slices.annotations]] +[[snippet.slices.annotations]] label = "value moved here" annotation_type = "Warning" range = [8, 9] -[[slices]] +[[snippet.slices]] source = "x;" line_start = 9 -[[slices.annotations]] +[[snippet.slices.annotations]] label = "value used here after move" annotation_type = "Error" range = [0, 1] diff --git a/tests/fixtures/no-color/multiline_annotation.toml b/tests/fixtures/no-color/multiline_annotation.toml index c3dc1e9e..604e04b0 100644 --- a/tests/fixtures/no-color/multiline_annotation.toml +++ b/tests/fixtures/no-color/multiline_annotation.toml @@ -1,4 +1,4 @@ -[[slices]] +[[snippet.slices]] source = """ ) -> Option { for ann in annotations { @@ -26,15 +26,15 @@ source = """ line_start = 51 origin = "src/format.rs" fold = true -[[slices.annotations]] +[[snippet.slices.annotations]] label = "expected `std::option::Option` because of return type" annotation_type = "Warning" range = [5, 19] -[[slices.annotations]] +[[snippet.slices.annotations]] label = "expected enum `std::option::Option`, found ()" annotation_type = "Error" range = [22, 766] -[title] +[snippet.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 index 845bf9f2..3287fdce 100644 --- a/tests/fixtures/no-color/multiline_annotation2.toml +++ b/tests/fixtures/no-color/multiline_annotation2.toml @@ -1,4 +1,4 @@ -[[slices]] +[[snippet.slices]] source = """ if let DisplayLine::Source { ref mut inline_marks, @@ -7,12 +7,12 @@ source = """ line_start = 139 origin = "src/display_list.rs" fold = false -[[slices.annotations]] +[[snippet.slices.annotations]] label = "missing fields `lineno`, `content`" annotation_type = "Error" range = [31, 128] -[title] +[snippet.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 index 21bbcd85..9fe85fb4 100644 --- a/tests/fixtures/no-color/multiline_annotation3.toml +++ b/tests/fixtures/no-color/multiline_annotation3.toml @@ -1,4 +1,4 @@ -[[slices]] +[[snippet.slices]] source = """ This is an exampl e of an edge case of an annotation overflowing @@ -7,12 +7,12 @@ to exactly one character on next line. line_start = 26 origin = "foo.txt" fold = false -[[slices.annotations]] +[[snippet.slices.annotations]] label = "this should not be on separate lines" annotation_type = "Error" range = [11, 18] -[title] +[snippet.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 index 84efc5f1..f037c9a1 100644 --- a/tests/fixtures/no-color/multiple_annotations.toml +++ b/tests/fixtures/no-color/multiple_annotations.toml @@ -1,4 +1,4 @@ -[[slices]] +[[snippet.slices]] source = """ fn add_title_line(result: &mut Vec, main_annotation: Option<&Annotation>) { if let Some(annotation) = main_annotation { @@ -11,15 +11,15 @@ fn add_title_line(result: &mut Vec, main_annotation: Option<&Annotation> } """ line_start = 96 -[[slices.annotations]] +[[snippet.slices.annotations]] label = "Variable defined here" annotation_type = "Error" range = [100, 110] -[[slices.annotations]] +[[snippet.slices.annotations]] label = "Referenced here" annotation_type = "Error" range = [184, 194] -[[slices.annotations]] +[[snippet.slices.annotations]] label = "Referenced again here" annotation_type = "Error" range = [243, 253] diff --git a/tests/fixtures/no-color/simple.toml b/tests/fixtures/no-color/simple.toml index 6c38674a..2e9b6d89 100644 --- a/tests/fixtures/no-color/simple.toml +++ b/tests/fixtures/no-color/simple.toml @@ -1,18 +1,18 @@ -[[slices]] +[[snippet.slices]] source = """ }) for line in &self.body {""" line_start = 169 origin = "src/format_color.rs" -[[slices.annotations]] +[[snippet.slices.annotations]] label = "unexpected token" annotation_type = "Error" range = [20, 23] -[[slices.annotations]] +[[snippet.slices.annotations]] label = "expected one of `.`, `;`, `?`, or an operator here" annotation_type = "Warning" range = [10, 11] -[title] +[snippet.title] label = "expected one of `.`, `;`, `?`, or an operator, found `for`" -annotation_type = "Error" +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 76d9519b..4c609c45 100644 --- a/tests/fixtures/no-color/strip_line.toml +++ b/tests/fixtures/no-color/strip_line.toml @@ -1,22 +1,22 @@ -[title] +[snippet.title] id = "E0308" label = "mismatched types" annotation_type = "Error" -[[slices]] +[[snippet.slices]] source = " let _: () = 42;" line_start = 4 origin = "$DIR/whitespace-trimming.rs" -[[slices.annotations]] +[[snippet.slices.annotations]] label = "expected (), found integer" annotation_type = "Error" range = [192, 194] -[opt] +[renderer] color = false anonymized_line_numbers = true -[opt.margin] +[renderer.margin] whitespace_left = 180 span_left = 192 span_right = 194 diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/no-color/strip_line_char.toml index 5b432beb..76ea7499 100644 --- a/tests/fixtures/no-color/strip_line_char.toml +++ b/tests/fixtures/no-color/strip_line_char.toml @@ -1,22 +1,22 @@ -[title] +[snippet.title] id = "E0308" label = "mismatched types" annotation_type = "Error" -[[slices]] +[[snippet.slices]] source = " let _: () = 42ñ" line_start = 4 origin = "$DIR/whitespace-trimming.rs" -[[slices.annotations]] +[[snippet.slices.annotations]] label = "expected (), found integer" annotation_type = "Error" range = [192, 194] -[opt] +[renderer] color = false anonymized_line_numbers = true -[opt.margin] +[renderer.margin] whitespace_left = 180 span_left = 192 span_right = 194 diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/no-color/strip_line_non_ws.toml index 5129f5ce..c46d6e27 100644 --- a/tests/fixtures/no-color/strip_line_non_ws.toml +++ b/tests/fixtures/no-color/strip_line_non_ws.toml @@ -1,22 +1,21 @@ -[title] +[snippet.title] id = "E0308" label = "mismatched types" annotation_type = "Error" -[[slices]] +[[snippet.slices]] 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" -[[slices.annotations]] +[[snippet.slices.annotations]] label = "expected (), found integer" annotation_type = "Error" range = [240, 242] -[opt] -color = false +[renderer] anonymized_line_numbers = true -[opt.margin] +[renderer.margin] whitespace_left = 4 span_left = 240 span_right = 242 diff --git a/tests/fixtures_test.rs b/tests/fixtures_test.rs index 13f85431..063829a6 100644 --- a/tests/fixtures_test.rs +++ b/tests/fixtures_test.rs @@ -1,8 +1,9 @@ +mod deserialize; mod diff; -mod snippet; -use crate::snippet::SnippetDef; -use annotate_snippets::{display_list::DisplayList, snippet::Snippet}; +use crate::deserialize::Fixture; +use annotate_snippets::Renderer; +use annotate_snippets::Snippet; use glob::glob; use std::{error::Error, fs::File, io, io::prelude::*}; @@ -13,8 +14,8 @@ fn read_file(path: &str) -> Result { Ok(s.trim_end().to_string()) } -fn read_fixture(src: &str) -> Result, Box> { - Ok(toml::from_str(src).map(|a: SnippetDef| a.into())?) +fn read_fixture(src: &str) -> Result<(Renderer, Snippet<'_>), Box> { + Ok(toml::from_str(src).map(|a: Fixture| (a.renderer.into(), a.snippet.into()))?) } #[test] @@ -27,11 +28,10 @@ fn test_fixtures() { let path_out = path_in.replace(".toml", ".txt"); let src = read_file(path_in).expect("Failed to read file"); - let snippet = read_fixture(&src).expect("Failed to read file"); + let (renderer, 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); - let actual_out = dl.to_string(); + let actual_out = renderer.render(snippet).to_string(); println!("{}", expected_out); println!("{}", actual_out.trim_end()); diff --git a/tests/formatter.rs b/tests/formatter.rs index f95b0026..97c7be3b 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1,544 +1,25 @@ -use annotate_snippets::display_list::*; -use annotate_snippets::snippet::{self, Snippet}; - -#[test] -fn test_source_empty() { - let dl = DisplayList::from(vec![DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }]); - - assert_eq!(dl.to_string(), " |"); -} - -#[test] -fn test_source_content() { - let dl = DisplayList::from(vec![ - DisplayLine::Source { - lineno: Some(56), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "This is an example", - range: (0, 19), - }, - }, - DisplayLine::Source { - lineno: Some(57), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "of content lines", - range: (0, 19), - }, - }, - ]); - - assert_eq!( - dl.to_string(), - "56 | This is an example\n57 | of content lines" - ); -} - -#[test] -fn test_source_annotation_standalone_singleline() { - let dl = DisplayList::from(vec![DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::None, - id: None, - label: vec![DisplayTextFragment { - content: "Example string", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Error, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }]); - - assert_eq!(dl.to_string(), " | ^^^^^ Example string"); -} - -#[test] -fn test_source_annotation_standalone_multiline() { - let dl = DisplayList::from(vec![ - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Help, - id: None, - label: vec![DisplayTextFragment { - content: "Example string", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Warning, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Help, - id: None, - label: vec![DisplayTextFragment { - content: "Second line", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Warning, - annotation_part: DisplayAnnotationPart::LabelContinuation, - }, - }, - ]); - - assert_eq!( - dl.to_string(), - " | ----- help: Example string\n | Second line" - ); -} - -#[test] -fn test_source_annotation_standalone_multi_annotation() { - let dl = DisplayList::from(vec![ - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Info, - id: None, - label: vec![DisplayTextFragment { - content: "Example string", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Note, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Info, - id: None, - label: vec![DisplayTextFragment { - content: "Second line", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Note, - annotation_part: DisplayAnnotationPart::LabelContinuation, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Warning, - id: None, - label: vec![DisplayTextFragment { - content: "This is a note", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Note, - annotation_part: DisplayAnnotationPart::Consequitive, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Warning, - id: None, - label: vec![DisplayTextFragment { - content: "Second line of the warning", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Note, - annotation_part: DisplayAnnotationPart::LabelContinuation, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Info, - id: None, - label: vec![DisplayTextFragment { - content: "This is an info", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Info, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Help, - id: None, - label: vec![DisplayTextFragment { - content: "This is help", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Help, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 0), - annotation: Annotation { - annotation_type: DisplayAnnotationType::None, - id: None, - label: vec![DisplayTextFragment { - content: "This is an annotation of type none", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::None, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - ]); - - assert_eq!(dl.to_string(), " | ----- info: Example string\n | Second line\n | warning: This is a note\n | Second line of the warning\n | ----- info: This is an info\n | ----- help: This is help\n | This is an annotation of type none"); -} - -#[test] -fn test_fold_line() { - let dl = DisplayList::from(vec![ - DisplayLine::Source { - lineno: Some(5), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "This is line 5", - range: (0, 19), - }, - }, - DisplayLine::Fold { - inline_marks: vec![], - }, - DisplayLine::Source { - lineno: Some(10021), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "... and now we're at line 10021", - range: (0, 19), - }, - }, - ]); - - assert_eq!( - dl.to_string(), - " 5 | This is line 5\n...\n10021 | ... and now we're at line 10021" - ); -} - -#[test] -fn test_raw_origin_initial_nopos() { - let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { - path: "src/test.rs", - pos: None, - header_type: DisplayHeaderType::Initial, - })]); - - assert_eq!(dl.to_string(), "--> src/test.rs"); -} - -#[test] -fn test_raw_origin_initial_pos() { - let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { - path: "src/test.rs", - pos: Some((23, 15)), - header_type: DisplayHeaderType::Initial, - })]); - - assert_eq!(dl.to_string(), "--> src/test.rs:23:15"); -} - -#[test] -fn test_raw_origin_continuation() { - let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { - path: "src/test.rs", - pos: Some((23, 15)), - header_type: DisplayHeaderType::Continuation, - })]); - - assert_eq!(dl.to_string(), "::: src/test.rs:23:15"); -} - -#[test] -fn test_raw_annotation_unaligned() { - let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Error, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "This is an error", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: false, - continuation: false, - })]); - - assert_eq!(dl.to_string(), "error[E0001]: This is an error"); -} - -#[test] -fn test_raw_annotation_unaligned_multiline() { - let dl = DisplayList::from(vec![ - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Warning, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "This is an error", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: false, - continuation: false, - }), - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Warning, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "Second line of the error", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: false, - continuation: true, - }), - ]); - - assert_eq!( - dl.to_string(), - "warning[E0001]: This is an error\n Second line of the error" - ); -} - -#[test] -fn test_raw_annotation_aligned() { - let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Error, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "This is an error", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: true, - continuation: false, - })]); - - assert_eq!(dl.to_string(), " = error[E0001]: This is an error"); -} - -#[test] -fn test_raw_annotation_aligned_multiline() { - let dl = DisplayList::from(vec![ - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Warning, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "This is an error", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: true, - continuation: false, - }), - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Warning, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "Second line of the error", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: true, - continuation: true, - }), - ]); - - assert_eq!( - dl.to_string(), - " = warning[E0001]: This is an error\n Second line of the error" - ); -} - -#[test] -fn test_different_annotation_types() { - let dl = DisplayList::from(vec![ - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Note, - id: None, - label: vec![DisplayTextFragment { - content: "This is a note", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: false, - continuation: false, - }), - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::None, - id: None, - label: vec![DisplayTextFragment { - content: "This is just a string", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: false, - continuation: false, - }), - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::None, - id: None, - label: vec![DisplayTextFragment { - content: "Second line of none type annotation", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: false, - continuation: true, - }), - ]); - - assert_eq!( - dl.to_string(), - "note: This is a note\nThis is just a string\n Second line of none type annotation", - ); -} - -#[test] -fn test_inline_marks_empty_line() { - let dl = DisplayList::from(vec![DisplayLine::Source { - lineno: None, - inline_marks: vec![DisplayMark { - mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: DisplayAnnotationType::Error, - }], - line: DisplaySourceLine::Empty, - }]); - - assert_eq!(dl.to_string(), " | |",); -} - -#[test] -fn test_anon_lines() { - let mut dl = DisplayList::from(vec![ - DisplayLine::Source { - lineno: Some(56), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "This is an example", - range: (0, 19), - }, - }, - DisplayLine::Source { - lineno: Some(57), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "of content lines", - range: (0, 19), - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "abc", - range: (0, 19), - }, - }, - ]); - - dl.anonymized_line_numbers = true; - assert_eq!( - dl.to_string(), - "LL | This is an example\nLL | of content lines\n |\n | abc" - ); -} - -#[test] -fn test_raw_origin_initial_pos_anon_lines() { - let mut dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { - path: "src/test.rs", - pos: Some((23, 15)), - header_type: DisplayHeaderType::Initial, - })]); - - // Using anonymized_line_numbers should not affect the initial position - dl.anonymized_line_numbers = true; - assert_eq!(dl.to_string(), "--> src/test.rs:23:15"); -} +use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation}; #[test] fn test_i_29() { let snippets = Snippet { - title: Some(snippet::Annotation { + title: Some(Annotation { id: None, label: Some("oops"), - annotation_type: snippet::AnnotationType::Error, + annotation_type: AnnotationType::Error, }), footer: vec![], - slices: vec![snippet::Slice { + slices: vec![Slice { source: "First line\r\nSecond oops line", line_start: 1, origin: Some(""), - annotations: vec![snippet::SourceAnnotation { + annotations: vec![SourceAnnotation { range: (19, 23), label: "oops", - annotation_type: snippet::AnnotationType::Error, + annotation_type: AnnotationType::Error, }], fold: true, }], - opt: Default::default(), }; let expected = r#"error: oops --> :2:8 @@ -548,26 +29,26 @@ fn test_i_29() { | ^^^^ oops |"#; - assert_eq!(DisplayList::from(snippets).to_string(), expected); + let renderer = Renderer::plain(); + assert_eq!(renderer.render(snippets).to_string(), expected); } #[test] fn test_point_to_double_width_characters() { let snippets = Snippet { - slices: vec![snippet::Slice { + slices: vec![Slice { source: "こんにちは、世界", line_start: 1, origin: Some(""), - annotations: vec![snippet::SourceAnnotation { + annotations: vec![SourceAnnotation { range: (6, 8), label: "world", - annotation_type: snippet::AnnotationType::Error, + annotation_type: AnnotationType::Error, }], fold: false, }], title: None, footer: vec![], - opt: Default::default(), }; let expected = r#" --> :1:7 @@ -576,26 +57,26 @@ fn test_point_to_double_width_characters() { | ^^^^ world |"#; - assert_eq!(DisplayList::from(snippets).to_string(), expected); + let renderer = Renderer::plain(); + assert_eq!(renderer.render(snippets).to_string(), expected); } #[test] fn test_point_to_double_width_characters_across_lines() { let snippets = Snippet { - slices: vec![snippet::Slice { + slices: vec![Slice { source: "おはよう\nございます", line_start: 1, origin: Some(""), - annotations: vec![snippet::SourceAnnotation { + annotations: vec![SourceAnnotation { range: (2, 8), label: "Good morning", - annotation_type: snippet::AnnotationType::Error, + annotation_type: AnnotationType::Error, }], fold: false, }], title: None, footer: vec![], - opt: Default::default(), }; let expected = r#" --> :1:3 @@ -606,33 +87,33 @@ fn test_point_to_double_width_characters_across_lines() { | |______^ Good morning |"#; - assert_eq!(DisplayList::from(snippets).to_string(), expected); + let renderer = Renderer::plain(); + assert_eq!(renderer.render(snippets).to_string(), expected); } #[test] fn test_point_to_double_width_characters_multiple() { let snippets = Snippet { - slices: vec![snippet::Slice { + slices: vec![Slice { source: "お寿司\n食べたい🍣", line_start: 1, origin: Some(""), annotations: vec![ - snippet::SourceAnnotation { + SourceAnnotation { range: (0, 3), label: "Sushi1", - annotation_type: snippet::AnnotationType::Error, + annotation_type: AnnotationType::Error, }, - snippet::SourceAnnotation { + SourceAnnotation { range: (6, 8), label: "Sushi2", - annotation_type: snippet::AnnotationType::Note, + annotation_type: AnnotationType::Note, }, ], fold: false, }], title: None, footer: vec![], - opt: Default::default(), }; let expected = r#" --> :1:1 @@ -643,26 +124,26 @@ fn test_point_to_double_width_characters_multiple() { | ---- note: Sushi2 |"#; - assert_eq!(DisplayList::from(snippets).to_string(), expected); + let renderer = Renderer::plain(); + assert_eq!(renderer.render(snippets).to_string(), expected); } #[test] fn test_point_to_double_width_characters_mixed() { let snippets = Snippet { - slices: vec![snippet::Slice { + slices: vec![Slice { source: "こんにちは、新しいWorld!", line_start: 1, origin: Some(""), - annotations: vec![snippet::SourceAnnotation { + annotations: vec![SourceAnnotation { range: (6, 14), label: "New world", - annotation_type: snippet::AnnotationType::Error, + annotation_type: AnnotationType::Error, }], fold: false, }], title: None, footer: vec![], - opt: Default::default(), }; let expected = r#" --> :1:7 @@ -671,5 +152,6 @@ fn test_point_to_double_width_characters_mixed() { | ^^^^^^^^^^^ New world |"#; - assert_eq!(DisplayList::from(snippets).to_string(), expected); + let renderer = Renderer::plain(); + assert_eq!(renderer.render(snippets).to_string(), expected); }