diff --git a/benches/simple.rs b/benches/simple.rs index 9633260..a0da903 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -1,12 +1,13 @@ +#![allow(clippy::unit_arg)] #[macro_use] extern crate criterion; -use criterion::black_box; -use criterion::Criterion; +use criterion::{black_box, Criterion}; -use annotate_snippets::display_list::DisplayList; -use annotate_snippets::formatter::DisplayListFormatter; -use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; +use annotate_snippets::{ + display_list::{DisplayList, FormatOptions}, + snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, +}; fn create_snippet() { let snippet = Snippet { @@ -46,7 +47,7 @@ fn create_snippet() { SourceAnnotation { label: "expected enum `std::option::Option`".to_string(), annotation_type: AnnotationType::Error, - range: (23, 745), + range: (26, 724), }, ], }], @@ -56,11 +57,14 @@ fn create_snippet() { annotation_type: AnnotationType::Error, }), footer: vec![], + opt: FormatOptions { + color: true, + ..Default::default() + }, }; let dl = DisplayList::from(snippet); - let dlf = DisplayListFormatter::new(true, false); - let _result = dlf.format(&dl); + let _result = dl.to_string(); } pub fn criterion_benchmark(c: &mut Criterion) { diff --git a/examples/expected_type.rs b/examples/expected_type.rs index 20cda66..b12ebad 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -1,6 +1,7 @@ -use annotate_snippets::display_list::DisplayList; -use annotate_snippets::formatter::DisplayListFormatter; -use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; +use annotate_snippets::{ + display_list::{DisplayList, FormatOptions}, + snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, +}; fn main() { let snippet = Snippet { @@ -23,7 +24,7 @@ fn main() { SourceAnnotation { label: "".to_string(), annotation_type: AnnotationType::Error, - range: (208, 210), + range: (205, 207), }, SourceAnnotation { label: "while parsing this struct".to_string(), @@ -32,9 +33,12 @@ fn main() { }, ], }], + opt: FormatOptions { + color: true, + ..Default::default() + }, }; let dl = DisplayList::from(snippet); - let dlf = DisplayListFormatter::new(true, false); - println!("{}", dlf.format(&dl)); + println!("{}", dl); } diff --git a/examples/footer.rs b/examples/footer.rs index 1b03910..4e331d3 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -1,6 +1,7 @@ -use annotate_snippets::display_list::DisplayList; -use annotate_snippets::formatter::DisplayListFormatter; -use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; +use annotate_snippets::{ + display_list::{DisplayList, FormatOptions}, + snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, +}; fn main() { let snippet = Snippet { @@ -29,9 +30,12 @@ fn main() { annotation_type: AnnotationType::Error, }], }], + opt: FormatOptions { + color: true, + ..Default::default() + }, }; let dl = DisplayList::from(snippet); - let dlf = DisplayListFormatter::new(true, false); - println!("{}", dlf.format(&dl)); + println!("{}", dl); } diff --git a/examples/format.rs b/examples/format.rs index 7e41802..d8c73ee 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -1,6 +1,7 @@ -use annotate_snippets::display_list::DisplayList; -use annotate_snippets::formatter::DisplayListFormatter; -use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; +use annotate_snippets::{ + display_list::{DisplayList, FormatOptions}, + snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, +}; fn main() { let snippet = Snippet { @@ -40,7 +41,7 @@ fn main() { SourceAnnotation { label: "expected enum `std::option::Option`".to_string(), annotation_type: AnnotationType::Error, - range: (23, 745), + range: (26, 724), }, ], }], @@ -50,9 +51,12 @@ fn main() { annotation_type: AnnotationType::Error, }), footer: vec![], + opt: FormatOptions { + color: true, + ..Default::default() + }, }; let dl = DisplayList::from(snippet); - let dlf = DisplayListFormatter::new(true, false); - println!("{}", dlf.format(&dl)); + println!("{}", dl); } diff --git a/examples/multislice.rs b/examples/multislice.rs index af1cb15..fe71977 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -1,6 +1,7 @@ -use annotate_snippets::display_list::DisplayList; -use annotate_snippets::formatter::DisplayListFormatter; -use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet}; +use annotate_snippets::{ + display_list::{DisplayList, FormatOptions}, + snippet::{Annotation, AnnotationType, Slice, Snippet}, +}; fn main() { let snippet = Snippet { @@ -26,9 +27,12 @@ fn main() { annotations: vec![], }, ], + opt: FormatOptions { + color: true, + ..Default::default() + }, }; let dl = DisplayList::from(snippet); - let dlf = DisplayListFormatter::new(true, false); - println!("{}", dlf.format(&dl)); + println!("{}", dl); } diff --git a/src/display_list/from_snippet.rs b/src/display_list/from_snippet.rs index 2ba3b70..d9e1dd3 100644 --- a/src/display_list/from_snippet.rs +++ b/src/display_list/from_snippet.rs @@ -1,12 +1,11 @@ //! Trait for converting `Snippet` to `DisplayList`. use super::*; -use crate::snippet; +use crate::{formatter::get_term_style, snippet}; fn format_label(label: Option<&str>, style: Option) -> Vec { let mut result = vec![]; if let Some(label) = label { - let elements: Vec<&str> = label.split("__").collect(); - for (idx, element) in elements.iter().enumerate() { + for (idx, element) in label.split("__").enumerate() { let element_style = match style { Some(s) => s, None => { @@ -26,12 +25,12 @@ fn format_label(label: Option<&str>, style: Option) -> Vec DisplayLine { - let label = annotation.label.clone().unwrap_or_default(); +fn format_title(annotation: snippet::Annotation) -> DisplayLine { + let label = annotation.label.unwrap_or_default(); DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::from(annotation.annotation_type), - id: annotation.id.clone(), + id: annotation.id, label: format_label(Some(&label), Some(DisplayTextStyle::Emphasis)), }, source_aligned: false, @@ -39,9 +38,9 @@ fn format_title(annotation: &snippet::Annotation) -> DisplayLine { }) } -fn format_annotation(annotation: &snippet::Annotation) -> Vec { +fn format_annotation(annotation: snippet::Annotation) -> Vec { let mut result = vec![]; - let label = annotation.label.clone().unwrap_or_default(); + let label = annotation.label.unwrap_or_default(); for (i, line) in label.lines().enumerate() { result.push(DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { @@ -56,11 +55,14 @@ fn format_annotation(annotation: &snippet::Annotation) -> Vec { result } -fn format_slice(slice: &snippet::Slice, is_first: bool, has_footer: bool) -> Vec { +fn format_slice(mut slice: snippet::Slice, is_first: bool, has_footer: bool) -> Vec { + let main_range = slice.annotations.get(0).map(|x| x.range.0); + let row = slice.line_start; + let origin = slice.origin.take(); let mut body = format_body(slice, has_footer); + let header = format_header(origin, main_range, row, &body, is_first); let mut result = vec![]; - let header = format_header(slice, &body, is_first); if let Some(header) = header { result.push(header); } @@ -69,46 +71,45 @@ fn format_slice(slice: &snippet::Slice, is_first: bool, has_footer: bool) -> Vec } fn format_header( - slice: &snippet::Slice, + origin: Option, + main_range: Option, + mut row: usize, body: &[DisplayLine], is_first: bool, ) -> Option { - let main_annotation = slice.annotations.get(0); - let display_header = if is_first { DisplayHeaderType::Initial } else { DisplayHeaderType::Continuation }; - if let Some(annotation) = main_annotation { + if let Some(main_range) = main_range { let mut col = 1; - let mut row = slice.line_start; - for item in body.iter() { + for item in body { if let DisplayLine::Source { line: DisplaySourceLine::Content { range, .. }, .. } = item { - if annotation.range.0 >= range.0 && annotation.range.0 <= range.1 { - col = annotation.range.0 - range.0 + 1; + if main_range >= range.0 && main_range <= range.1 { + col = main_range - range.0 + 1; break; } row += 1; } } - if let Some(ref path) = slice.origin { + if let Some(path) = origin { return Some(DisplayLine::Raw(DisplayRawLine::Origin { - path: path.to_string(), + path, pos: Some((row, col)), header_type: display_header, })); } } - if let Some(ref path) = slice.origin { + if let Some(path) = origin { return Some(DisplayLine::Raw(DisplayRawLine::Origin { - path: path.to_string(), + path, pos: None, header_type: display_header, })); @@ -175,15 +176,30 @@ fn fold_body(body: &[DisplayLine]) -> Vec { new_body } -fn format_body(slice: &snippet::Slice, has_footer: bool) -> Vec { - let mut body = vec![]; +fn format_body(slice: snippet::Slice, has_footer: bool) -> Vec { + let source_len = slice.source.chars().count(); + if let Some(bigger) = slice.annotations.iter().find_map(|x| { + if source_len < x.range.1 { + Some(x.range) + } else { + None + } + }) { + panic!( + "SourceAnnotation range `{:?}` is bigger than source length `{}`", + bigger, source_len + ) + } + let mut body = vec![]; let mut current_line = slice.line_start; let mut current_index = 0; let mut line_index_ranges = vec![]; - for line in slice.source.lines() { - let line_length = line.chars().count() + 1; + let lines = slice.source.lines(); + let lines_len = lines.clone().count(); + for (i, line) in lines.enumerate() { + let line_length = line.chars().count(); let line_range = (current_index, current_index + line_length); body.push(DisplayLine::Source { lineno: Some(current_line), @@ -195,13 +211,14 @@ fn format_body(slice: &snippet::Slice, has_footer: bool) -> Vec { }); line_index_ranges.push(line_range); current_line += 1; - current_index += line_length + 1; + if i + 1 < lines_len { + current_index += line_length + 1; + } } let mut annotation_line_count = 0; - let mut annotations = slice.annotations.clone(); - for idx in 0..body.len() { - let (line_start, line_end) = line_index_ranges[idx]; + let mut annotations = slice.annotations; + for (idx, (line_start, line_end)) in line_index_ranges.into_iter().enumerate() { // It would be nice to use filter_drain here once it's stable. annotations = annotations .into_iter() @@ -214,7 +231,10 @@ fn format_body(slice: &snippet::Slice, has_footer: bool) -> Vec { }; match annotation.range { (start, _) if start > line_end => true, - (start, end) if start >= line_start && end <= line_end => { + (start, end) + if start >= line_start && end <= line_end + || start == line_end && end - start <= 1 => + { let range = (start - line_start, end - line_start); body.insert( body_idx + 1, @@ -305,6 +325,7 @@ fn format_body(slice: &snippet::Slice, has_footer: bool) -> Vec { ), }); } + let range = (end - line_start, end - line_start + 1); body.insert( body_idx + 1, @@ -367,26 +388,39 @@ fn format_body(slice: &snippet::Slice, has_footer: bool) -> Vec { body } +// TODO: From reference to DisplayList<'a> impl From for DisplayList { - fn from(snippet: snippet::Snippet) -> Self { + fn from( + snippet::Snippet { + title, + footer, + slices, + opt, + }: snippet::Snippet, + ) -> Self { let mut body = vec![]; - if let Some(annotation) = snippet.title { - body.push(format_title(&annotation)); + if let Some(annotation) = title { + body.push(format_title(annotation)); } - for (idx, slice) in snippet.slices.iter().enumerate() { - body.append(&mut format_slice( - &slice, - idx == 0, - !snippet.footer.is_empty(), - )); + for (idx, slice) in slices.into_iter().enumerate() { + body.append(&mut format_slice(slice, idx == 0, !footer.is_empty())); } - for annotation in snippet.footer { - body.append(&mut format_annotation(&annotation)); + for annotation in footer { + body.append(&mut format_annotation(annotation)); } - Self { body } + let FormatOptions { + color, + anonymized_line_numbers, + } = opt; + + Self { + body, + stylesheet: get_term_style(color), + anonymized_line_numbers, + } } } diff --git a/src/display_list/mod.rs b/src/display_list/mod.rs index 5e0b393..224a9f5 100644 --- a/src/display_list/mod.rs +++ b/src/display_list/mod.rs @@ -27,97 +27,10 @@ //! are `Source` lines. //! //! `DisplayList` does not store column alignment information, and those are -//! only calculated by the `DisplayListFormatter` using information such as +//! only calculated by the implementation of `std::fmt::Display` using information such as //! styling. //! //! The above snippet has been built out of the following structure: -//! -//! ``` -//! use annotate_snippets::display_list::*; -//! -//! let dl = DisplayList { -//! body: vec![ -//! DisplayLine::Raw(DisplayRawLine::Annotation { -//! annotation: Annotation { -//! annotation_type: DisplayAnnotationType::Error, -//! id: Some("E0308".to_string()), -//! label: vec![ -//! DisplayTextFragment { -//! content: "mismatched types".to_string(), -//! style: DisplayTextStyle::Regular, -//! } -//! ] -//! }, -//! source_aligned: false, -//! continuation: false, -//! }), -//! DisplayLine::Raw(DisplayRawLine::Origin { -//! path: "src/format.rs".to_string(), -//! pos: Some((51, 5)), -//! header_type: DisplayHeaderType::Initial, -//! }), -//! DisplayLine::Source { -//! lineno: Some(151), -//! inline_marks: vec![ -//! DisplayMark { -//! mark_type: DisplayMarkType::AnnotationStart, -//! annotation_type: DisplayAnnotationType::Error, -//! } -//! ], -//! line: DisplaySourceLine::Content { -//! text: " fn test() -> String {".to_string(), -//! range: (0, 24) -//! } -//! }, -//! DisplayLine::Source { -//! lineno: Some(152), -//! inline_marks: vec![ -//! DisplayMark { -//! mark_type: DisplayMarkType::AnnotationThrough, -//! annotation_type: DisplayAnnotationType::Error, -//! } -//! ], -//! line: DisplaySourceLine::Content { -//! text: " return \"test\";".to_string(), -//! range: (25, 46) -//! } -//! }, -//! DisplayLine::Source { -//! lineno: Some(153), -//! inline_marks: vec![ -//! DisplayMark { -//! mark_type: DisplayMarkType::AnnotationThrough, -//! annotation_type: DisplayAnnotationType::Error, -//! } -//! ], -//! line: DisplaySourceLine::Content { -//! text: " }".to_string(), -//! range: (47, 51) -//! } -//! }, -//! DisplayLine::Source { -//! lineno: None, -//! inline_marks: vec![], -//! line: DisplaySourceLine::Annotation { -//! annotation: Annotation { -//! annotation_type: DisplayAnnotationType::Error, -//! id: None, -//! label: vec![ -//! DisplayTextFragment { -//! content: "expected `String`, for `&str`.".to_string(), -//! style: DisplayTextStyle::Regular, -//! } -//! ] -//! }, -//! range: (3, 4), -//! annotation_type: DisplayAnnotationType::Error, -//! annotation_part: DisplayAnnotationPart::MultilineEnd, -//! } -//! -//! } -//! ] -//! }; -//! ``` mod from_snippet; mod structs; diff --git a/src/display_list/structs.rs b/src/display_list/structs.rs index 0d3e0bc..07c52ae 100644 --- a/src/display_list/structs.rs +++ b/src/display_list/structs.rs @@ -1,15 +1,45 @@ +use std::fmt; + +use crate::formatter::{get_term_style, style::Stylesheet}; + /// List of lines to be displayed. -#[derive(Debug, Clone, PartialEq)] pub struct DisplayList { pub body: Vec, + pub stylesheet: Box, + pub anonymized_line_numbers: bool, } impl From> for DisplayList { fn from(body: Vec) -> Self { - Self { body } + Self { + body, + anonymized_line_numbers: false, + stylesheet: get_term_style(false), + } + } +} + +impl PartialEq for DisplayList { + fn eq(&self, other: &Self) -> bool { + self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers } } +impl fmt::Debug for DisplayList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DisplayList") + .field("body", &self.body) + .field("anonymized_line_numbers", &self.anonymized_line_numbers) + .finish() + } +} + +#[derive(Debug, Default, Copy, Clone)] +pub struct FormatOptions { + pub color: bool, + pub anonymized_line_numbers: bool, +} + /// Inline annotation which can be used in either Raw or Source line. #[derive(Debug, Clone, PartialEq)] pub struct Annotation { @@ -125,63 +155,8 @@ pub struct DisplayMark { #[derive(Debug, Clone, PartialEq)] pub enum DisplayMarkType { /// A mark indicating a multiline annotation going through the current line. - /// - /// Example: - /// ``` - /// use annotate_snippets::display_list::*; - /// use annotate_snippets::formatter::DisplayListFormatter; - /// - /// let dlf = DisplayListFormatter::new(false, false); // Don't use colors - /// - /// let dl = DisplayList { - /// body: vec![ - /// DisplayLine::Source { - /// lineno: Some(51), - /// inline_marks: vec![ - /// DisplayMark { - /// mark_type: DisplayMarkType::AnnotationThrough, - /// annotation_type: DisplayAnnotationType::Error, - /// } - /// ], - /// line: DisplaySourceLine::Content { - /// text: "Example".to_string(), - /// range: (0, 7), - /// } - /// } - /// ] - /// }; - /// assert_eq!(dlf.format(&dl), "51 | | Example"); - /// ``` AnnotationThrough, - /// A mark indicating a multiline annotation starting on the given line. - /// - /// Example: - /// ``` - /// use annotate_snippets::display_list::*; - /// use annotate_snippets::formatter::DisplayListFormatter; - /// - /// let dlf = DisplayListFormatter::new(false, false); // Don't use colors - /// - /// let dl = DisplayList { - /// body: vec![ - /// DisplayLine::Source { - /// lineno: Some(51), - /// inline_marks: vec![ - /// DisplayMark { - /// mark_type: DisplayMarkType::AnnotationStart, - /// annotation_type: DisplayAnnotationType::Error, - /// } - /// ], - /// line: DisplaySourceLine::Content { - /// text: "Example".to_string(), - /// range: (0, 7), - /// } - /// } - /// ] - /// }; - /// assert_eq!(dlf.format(&dl), "51 | / Example"); - /// ``` AnnotationStart, } @@ -205,49 +180,12 @@ pub enum DisplayAnnotationType { /// Information whether the header is the initial one or a consequitive one /// for multi-slice cases. +// TODO: private #[derive(Debug, Clone, PartialEq)] pub enum DisplayHeaderType { /// Initial header is the first header in the snippet. - /// - /// Example: - /// ``` - /// use annotate_snippets::display_list::*; - /// use annotate_snippets::formatter::DisplayListFormatter; - /// - /// let dlf = DisplayListFormatter::new(false, false); // Don't use colors - /// - /// let dl = DisplayList { - /// body: vec![ - /// DisplayLine::Raw(DisplayRawLine::Origin { - /// path: "file1.rs".to_string(), - /// pos: Some((51, 5)), - /// header_type: DisplayHeaderType::Initial, - /// }) - /// ] - /// }; - /// assert_eq!(dlf.format(&dl), "--> file1.rs:51:5"); - /// ``` Initial, /// Continuation marks all headers of following slices in the snippet. - /// - /// Example: - /// ``` - /// use annotate_snippets::display_list::*; - /// use annotate_snippets::formatter::DisplayListFormatter; - /// - /// let dlf = DisplayListFormatter::new(false, false); // Don't use colors - /// - /// let dl = DisplayList { - /// body: vec![ - /// DisplayLine::Raw(DisplayRawLine::Origin { - /// path: "file1.rs".to_string(), - /// pos: Some((51, 5)), - /// header_type: DisplayHeaderType::Continuation, - /// }) - /// ] - /// }; - /// assert_eq!(dlf.format(&dl), "::: file1.rs:51:5"); - /// ``` Continuation, } diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs index b10a5e6..7812a4d 100644 --- a/src/formatter/mod.rs +++ b/src/formatter/mod.rs @@ -1,89 +1,73 @@ -//! DisplayListFormatter is a module handling the formatting of a -//! `DisplayList` into a formatted string. -//! -//! Besides formatting into a string it also uses a `style::Stylesheet` to -//! provide additional styling like colors and emphasis to the text. +use std::{ + cell::Cell, + cmp, + fmt::{self, Display, Formatter, Write}, +}; pub mod style; use self::style::{Style, StyleClass, Stylesheet}; -use crate::display_list::*; -use std::cmp; #[cfg(feature = "ansi_term")] use crate::stylesheets::color::AnsiTermStylesheet; -use crate::stylesheets::no_color::NoColorStylesheet; +use crate::{display_list::*, stylesheets::no_color::NoColorStylesheet}; + +pub struct DisplayFn) -> fmt::Result>(std::cell::Cell>); + +impl) -> fmt::Result> DisplayFn { + pub fn new(f: F) -> Self { + Self(Cell::new(Some(f))) + } +} + +impl) -> fmt::Result> Display for DisplayFn { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.0.take().ok_or(fmt::Error).and_then(|cl| cl(f)) + } +} fn repeat_char(c: char, n: usize) -> String { - let mut s = String::with_capacity(c.len_utf8()); - s.push(c); - s.repeat(n) + let mut s = String::with_capacity(c.len_utf8() * n); + for _ in 0..n { + s.push(c); + } + s } -/// DisplayListFormatter' constructor accepts two arguments: -/// -/// * `color` allows the formatter to optionally apply colors and emphasis -/// using the `ansi_term` crate. -/// * `anonymized_line_numbers` will replace line numbers in the left column with the text `LL`. -/// -/// Example: -/// -/// ``` -/// use annotate_snippets::formatter::DisplayListFormatter; -/// use annotate_snippets::display_list::{DisplayList, DisplayLine, DisplaySourceLine}; -/// -/// let dlf = DisplayListFormatter::new(false, false); // Don't use colors, Don't anonymize line numbers -/// -/// let dl = DisplayList { -/// body: vec![ -/// DisplayLine::Source { -/// lineno: Some(192), -/// inline_marks: vec![], -/// line: DisplaySourceLine::Content { -/// text: "Example line of text".into(), -/// range: (0, 21) -/// } -/// } -/// ] -/// }; -/// assert_eq!(dlf.format(&dl), "192 | Example line of text"); -/// ``` -pub struct DisplayListFormatter { - stylesheet: Box, - anonymized_line_numbers: bool, +fn format_repeat_char(c: char, n: usize, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for _ in 0..n { + f.write_char(c)?; + } + Ok(()) } -impl DisplayListFormatter { - const ANONYMIZED_LINE_NUM: &'static str = "LL"; +#[inline] +fn is_annotation_empty(annotation: &Annotation) -> bool { + annotation + .label + .iter() + .all(|fragment| fragment.content.is_empty()) +} - /// Constructor for the struct. - /// - /// The argument `color` selects the stylesheet depending on the user preferences and - /// `ansi_term` crate availability. - /// - /// The argument `anonymized_line_numbers` will replace line numbers in the left column with - /// the text `LL`. This can be useful to enable when running UI tests, such as in the Rust - /// test suite. - pub fn new(color: bool, anonymized_line_numbers: bool) -> Self { - if color { - Self { - #[cfg(feature = "ansi_term")] - stylesheet: Box::new(AnsiTermStylesheet {}), - #[cfg(not(feature = "ansi_term"))] - stylesheet: Box::new(NoColorStylesheet {}), - anonymized_line_numbers, - } - } else { - Self { - stylesheet: Box::new(NoColorStylesheet {}), - anonymized_line_numbers, - } - } +#[cfg(feature = "ansi_term")] +#[inline] +pub fn get_term_style(color: bool) -> Box { + if color { + Box::new(AnsiTermStylesheet) + } else { + Box::new(NoColorStylesheet) } +} - /// Formats a `DisplayList` into a String. - pub fn format(&self, dl: &DisplayList) -> String { - let lineno_width = dl.body.iter().fold(0, |max, line| match line { +#[cfg(not(feature = "ansi_term"))] +#[inline] +pub fn get_term_style(_color: bool) -> Box { + Box::new(NoColorStylesheet) +} + +impl fmt::Display for DisplayList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let lineno_width = self.body.iter().fold(0, |max, line| match line { DisplayLine::Source { lineno: Some(lineno), .. @@ -96,26 +80,36 @@ impl DisplayListFormatter { } _ => max, }); - let inline_marks_width = dl.body.iter().fold(0, |max, line| match line { + let inline_marks_width = self.body.iter().fold(0, |max, line| match line { DisplayLine::Source { inline_marks, .. } => cmp::max(inline_marks.len(), max), _ => max, }); - dl.body - .iter() - .map(|line| self.format_line(line, lineno_width, inline_marks_width)) - .collect::>() - .join("\n") + for (i, line) in self.body.iter().enumerate() { + self.format_line(line, lineno_width, inline_marks_width, f)?; + if i + 1 < self.body.len() { + f.write_char('\n')?; + } + } + Ok(()) } +} + +impl DisplayList { + const ANONYMIZED_LINE_NUM: &'static str = "LL"; - fn format_annotation_type(&self, annotation_type: &DisplayAnnotationType) -> &'static str { + fn format_annotation_type( + &self, + annotation_type: &DisplayAnnotationType, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { match annotation_type { - DisplayAnnotationType::Error => "error", - DisplayAnnotationType::Warning => "warning", - DisplayAnnotationType::Info => "info", - DisplayAnnotationType::Note => "note", - DisplayAnnotationType::Help => "help", - DisplayAnnotationType::None => "", + DisplayAnnotationType::Error => f.write_str("error"), + DisplayAnnotationType::Warning => f.write_str("warning"), + DisplayAnnotationType::Info => f.write_str("info"), + DisplayAnnotationType::Note => f.write_str("note"), + DisplayAnnotationType::Help => f.write_str("help"), + DisplayAnnotationType::None => Ok(()), } } @@ -130,16 +124,20 @@ impl DisplayListFormatter { }) } - fn format_label(&self, label: &[DisplayTextFragment]) -> String { + fn format_label( + &self, + label: &[DisplayTextFragment], + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { let emphasis_style = self.stylesheet.get_style(StyleClass::Emphasis); - label - .iter() - .map(|fragment| match fragment.style { - DisplayTextStyle::Regular => fragment.content.clone(), - DisplayTextStyle::Emphasis => emphasis_style.paint(&fragment.content), - }) - .collect::>() - .join("") + + for fragment in label { + match fragment.style { + DisplayTextStyle::Regular => fragment.content.fmt(f)?, + DisplayTextStyle::Emphasis => emphasis_style.paint(&fragment.content, f)?, + } + } + Ok(()) } fn format_annotation( @@ -147,42 +145,63 @@ impl DisplayListFormatter { annotation: &Annotation, continuation: bool, in_source: bool, - ) -> String { + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { let color = self.get_annotation_style(&annotation.annotation_type); - let formatted_type = if let Some(ref id) = annotation.id { - format!( - "{}[{}]", - self.format_annotation_type(&annotation.annotation_type), - id - ) + + let formatted_type = if let Some(id) = &annotation.id { + DisplayFn::new(|f| { + self.format_annotation_type(&annotation.annotation_type, f)?; + f.write_char('[')?; + f.write_str(id)?; + f.write_char(']') + }) + .to_string() } else { - self.format_annotation_type(&annotation.annotation_type) + DisplayFn::new(|f| self.format_annotation_type(&annotation.annotation_type, f)) .to_string() }; - let label = self.format_label(&annotation.label); - let label_part = if label.is_empty() { - "".to_string() - } else if in_source { - color.paint(&format!(": {}", self.format_label(&annotation.label))) - } else { - format!(": {}", self.format_label(&annotation.label)) - }; if continuation { let indent = formatted_type.len() + 2; - return format!("{}{}", repeat_char(' ', indent), label); + format_repeat_char(' ', indent, f)?; + return self.format_label(&annotation.label, f); } - if !formatted_type.is_empty() { - format!("{}{}", color.paint(&formatted_type), label_part) + if formatted_type.is_empty() { + self.format_label(&annotation.label, f) } else { - label + color.paint(&formatted_type, f)?; + if !is_annotation_empty(annotation) { + if in_source { + color.paint( + &DisplayFn::new(|f| { + f.write_str(": ")?; + self.format_label(&annotation.label, f) + }) + .to_string(), + f, + )?; + } else { + f.write_str(": ")?; + self.format_label(&annotation.label, f)?; + } + } + Ok(()) } } - fn format_source_line(&self, line: &DisplaySourceLine) -> Option { + #[inline] + fn format_source_line( + &self, + line: &DisplaySourceLine, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { match line { - DisplaySourceLine::Empty => None, - DisplaySourceLine::Content { text, .. } => Some(format!(" {}", text)), + DisplaySourceLine::Empty => Ok(()), + DisplaySourceLine::Content { text, .. } => { + f.write_char(' ')?; + text.fmt(f) + } DisplaySourceLine::Annotation { range, annotation, @@ -210,21 +229,32 @@ impl DisplayListFormatter { DisplayAnnotationPart::Consequitive => range.1, _ => range.0, }; - let indent = color.paint(&repeat_char(indent_char, indent_length + 1)); - let marks = color.paint(&repeat_char(mark, range.1 - indent_length)); - let annotation = self.format_annotation( - annotation, - annotation_part == &DisplayAnnotationPart::LabelContinuation, - true, - ); - if annotation.is_empty() { - return Some(format!("{}{}", indent, marks)); + + color.paint(&repeat_char(indent_char, indent_length + 1), f)?; + color.paint(&repeat_char(mark, range.1 - indent_length), f)?; + + if !is_annotation_empty(&annotation) { + f.write_char(' ')?; + color.paint( + &DisplayFn::new(|f| { + self.format_annotation( + annotation, + annotation_part == &DisplayAnnotationPart::LabelContinuation, + true, + f, + ) + }) + .to_string(), + f, + )?; } - Some(format!("{}{} {}", indent, marks, color.paint(&annotation))) + + Ok(()) } } } + #[inline] fn format_lineno(&self, lineno: Option, lineno_width: usize) -> String { match lineno { Some(n) => format!("{:>width$}", n, width = lineno_width), @@ -232,7 +262,13 @@ impl DisplayListFormatter { } } - fn format_raw_line(&self, line: &DisplayRawLine, lineno_width: usize) -> String { + #[inline] + fn format_raw_line( + &self, + line: &DisplayRawLine, + lineno_width: usize, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { match line { DisplayRawLine::Origin { path, @@ -246,21 +282,19 @@ impl DisplayListFormatter { let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); if let Some((col, row)) = pos { - format!( - "{}{} {}:{}:{}", - repeat_char(' ', lineno_width), - lineno_color.paint(header_sigil), - path, - col, - row - ) + format_repeat_char(' ', lineno_width, f)?; + lineno_color.paint(header_sigil, f)?; + f.write_char(' ')?; + path.fmt(f)?; + f.write_char(':')?; + col.fmt(f)?; + f.write_char(':')?; + row.fmt(f) } else { - format!( - "{}{} {}", - repeat_char(' ', lineno_width), - lineno_color.paint(header_sigil), - path - ) + format_repeat_char(' ', lineno_width, f)?; + lineno_color.paint(header_sigil, f)?; + f.write_char(' ')?; + path.fmt(f) } } DisplayRawLine::Annotation { @@ -270,75 +304,67 @@ impl DisplayListFormatter { } => { if *source_aligned { if *continuation { - format!( - "{}{}", - repeat_char(' ', lineno_width + 3), - self.format_annotation(annotation, *continuation, false) - ) + format_repeat_char(' ', lineno_width + 3, f)?; + self.format_annotation(annotation, *continuation, false, f) } else { let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); - format!( - "{} {} {}", - repeat_char(' ', lineno_width), - lineno_color.paint("="), - self.format_annotation(annotation, *continuation, false) - ) + format_repeat_char(' ', lineno_width, f)?; + f.write_char(' ')?; + lineno_color.paint("=", f)?; + f.write_char(' ')?; + self.format_annotation(annotation, *continuation, false, f) } } else { - self.format_annotation(annotation, *continuation, false) + self.format_annotation(annotation, *continuation, false, f) } } } } + #[inline] fn format_line( &self, dl: &DisplayLine, lineno_width: usize, inline_marks_width: usize, - ) -> String { + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { match dl { DisplayLine::Source { lineno, inline_marks, line, } => { - let lineno = if self.anonymized_line_numbers && lineno.is_some() { - Self::ANONYMIZED_LINE_NUM.to_string() - } else { - self.format_lineno(*lineno, lineno_width) - }; - let marks = self.format_inline_marks(inline_marks, inline_marks_width); - let lf = self.format_source_line(line); let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); - - let mut prefix = lineno_color.paint(&format!("{} |", lineno)); - - match lf { - Some(lf) => { - if !marks.is_empty() { - prefix.push_str(&format!(" {}", marks)); - } - format!("{}{}", prefix, lf) - } - None => { - if !marks.trim().is_empty() { - prefix.push_str(&format!(" {}", marks)); - } - prefix + if self.anonymized_line_numbers && lineno.is_some() { + lineno_color.paint(&format!("{} |", Self::ANONYMIZED_LINE_NUM), f)?; + } else { + lineno_color.paint( + &format!("{} |", self.format_lineno(*lineno, lineno_width)), + f, + )?; + } + if *line != DisplaySourceLine::Empty { + if !inline_marks.is_empty() || 0 < inline_marks_width { + f.write_char(' ')?; + self.format_inline_marks(inline_marks, inline_marks_width, f)?; } + self.format_source_line(line, f)?; + } else if !inline_marks.is_empty() { + f.write_char(' ')?; + self.format_inline_marks(inline_marks, inline_marks_width, f)?; } + Ok(()) } DisplayLine::Fold { inline_marks } => { - let marks = self.format_inline_marks(inline_marks, inline_marks_width); - let indent = lineno_width; - if marks.trim().is_empty() { - String::from("...") - } else { - format!("...{}{}", repeat_char(' ', indent), marks) + f.write_str("...")?; + if !inline_marks.is_empty() || 0 < inline_marks_width { + format_repeat_char(' ', lineno_width, f)?; + self.format_inline_marks(inline_marks, inline_marks_width, f)?; } + Ok(()) } - DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width), + DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width, f), } } @@ -346,22 +372,18 @@ impl DisplayListFormatter { &self, inline_marks: &[DisplayMark], inline_marks_width: usize, - ) -> String { - format!( - "{}{}", - " ".repeat(inline_marks_width - inline_marks.len()), - inline_marks - .iter() - .map(|mark| { - let sigil = match mark.mark_type { - DisplayMarkType::AnnotationThrough => "|", - DisplayMarkType::AnnotationStart => "/", - }; - let color = self.get_annotation_style(&mark.annotation_type); - color.paint(sigil) - }) - .collect::>() - .join(""), - ) + f: &mut fmt::Formatter<'_>, + ) -> 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( + match mark.mark_type { + DisplayMarkType::AnnotationThrough => "|", + DisplayMarkType::AnnotationStart => "/", + }, + f, + )?; + } + Ok(()) } } diff --git a/src/formatter/style.rs b/src/formatter/style.rs index c1b9046..c011ce3 100644 --- a/src/formatter/style.rs +++ b/src/formatter/style.rs @@ -5,57 +5,7 @@ //! formatter, a structs can implement `Stylesheet` and `Style` //! traits. //! -//! Example: -//! -//! ``` -//! use annotate_snippets::formatter::style::{Stylesheet, StyleClass, Style}; -//! -//! struct HTMLStyle { -//! prefix: String, -//! postfix: String, -//! }; -//! -//! impl HTMLStyle { -//! fn new(prefix: &str, postfix: &str) -> Self { -//! HTMLStyle { -//! prefix: prefix.into(), -//! postfix: postfix.into() -//! } -//! } -//! }; -//! -//! impl Style for HTMLStyle { -//! fn paint(&self, text: &str) -> String { -//! format!("{}{}{}", self.prefix, text, self.postfix) -//! } -//! -//! fn bold(&self) -> Box