--- /dev/null
+//! 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.
+
+pub mod style;
+
+use self::style::{Style, StyleClass, Stylesheet};
+use crate::display_list::*;
+use std::cmp;
+
+use crate::stylesheets::no_color::NoColorStylesheet;
+#[cfg(feature = "ansi_term")]
+use crate::stylesheets::color::AnsiTermStylesheet;
+
+fn repeat_char(c: char, n: usize) -> String {
+ let mut s = String::with_capacity(c.len_utf8());
+ s.push(c);
+ s.repeat(n)
+}
+
+/// 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<dyn Stylesheet>,
+ anonymized_line_numbers: bool,
+}
+
+impl DisplayListFormatter {
+ const ANONYMIZED_LINE_NUM: &'static str = "LL";
+
+ /// 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,
+ }
+ }
+ }
+
+ /// 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 {
+ DisplayLine::Source {
+ lineno: Some(lineno),
+ ..
+ } => {
+ if self.anonymized_line_numbers {
+ Self::ANONYMIZED_LINE_NUM.len()
+ } else {
+ cmp::max(lineno.to_string().len(), max)
+ }
+ },
+ _ => max,
+ });
+ let inline_marks_width = dl.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::<Vec<String>>()
+ .join("\n")
+ }
+
+ fn format_annotation_type(&self, annotation_type: &DisplayAnnotationType) -> &'static str {
+ match annotation_type {
+ DisplayAnnotationType::Error => "error",
+ DisplayAnnotationType::Warning => "warning",
+ DisplayAnnotationType::Info => "info",
+ DisplayAnnotationType::Note => "note",
+ DisplayAnnotationType::Help => "help",
+ DisplayAnnotationType::None => "",
+ }
+ }
+
+ fn get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> Box<dyn Style> {
+ 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 format_label(&self, label: &[DisplayTextFragment]) -> String {
+ 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::<Vec<String>>()
+ .join("")
+ }
+
+ fn format_annotation(
+ &self,
+ annotation: &Annotation,
+ continuation: bool,
+ in_source: bool,
+ ) -> String {
+ 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
+ )
+ } else {
+ self.format_annotation_type(&annotation.annotation_type)
+ .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);
+ }
+ if !formatted_type.is_empty() {
+ format!("{}{}", color.paint(&formatted_type), label_part)
+ } else {
+ label
+ }
+ }
+
+ fn format_source_line(&self, line: &DisplaySourceLine) -> Option<String> {
+ match line {
+ DisplaySourceLine::Empty => None,
+ DisplaySourceLine::Content { text, .. } => Some(format!(" {}", text)),
+ DisplaySourceLine::Annotation {
+ range,
+ annotation,
+ annotation_type,
+ annotation_part,
+ } => {
+ let indent_char = match annotation_part {
+ DisplayAnnotationPart::Standalone => ' ',
+ DisplayAnnotationPart::LabelContinuation => ' ',
+ DisplayAnnotationPart::Consequitive => ' ',
+ DisplayAnnotationPart::MultilineStart => '_',
+ DisplayAnnotationPart::MultilineEnd => '_',
+ };
+ let mark = match annotation_type {
+ DisplayAnnotationType::Error => '^',
+ DisplayAnnotationType::Warning => '-',
+ DisplayAnnotationType::Info => '-',
+ DisplayAnnotationType::Note => '-',
+ DisplayAnnotationType::Help => '-',
+ DisplayAnnotationType::None => ' ',
+ };
+ let color = self.get_annotation_style(annotation_type);
+ let indent_length = match annotation_part {
+ DisplayAnnotationPart::LabelContinuation => range.1,
+ 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));
+ }
+ Some(format!("{}{} {}", indent, marks, color.paint(&annotation)))
+ }
+ }
+ }
+
+ fn format_lineno(&self, lineno: Option<usize>, lineno_width: usize) -> String {
+ match lineno {
+ Some(n) => format!("{:>width$}", n, width = lineno_width),
+ None => repeat_char(' ', lineno_width),
+ }
+ }
+
+ fn format_raw_line(&self, line: &DisplayRawLine, lineno_width: usize) -> String {
+ match line {
+ DisplayRawLine::Origin {
+ path,
+ pos,
+ header_type,
+ } => {
+ let header_sigil = match header_type {
+ DisplayHeaderType::Initial => "-->",
+ DisplayHeaderType::Continuation => ":::",
+ };
+ 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
+ )
+ } else {
+ format!(
+ "{}{} {}",
+ repeat_char(' ', lineno_width),
+ lineno_color.paint(header_sigil),
+ path
+ )
+ }
+ }
+ DisplayRawLine::Annotation {
+ annotation,
+ source_aligned,
+ continuation,
+ } => {
+ if *source_aligned {
+ if *continuation {
+ format!(
+ "{}{}",
+ repeat_char(' ', lineno_width + 3),
+ self.format_annotation(annotation, *continuation, false)
+ )
+ } else {
+ let lineno_color = self.stylesheet.get_style(StyleClass::LineNo);
+ format!(
+ "{} {} {}",
+ repeat_char(' ', lineno_width),
+ lineno_color.paint("="),
+ self.format_annotation(annotation, *continuation, false)
+ )
+ }
+ } else {
+ self.format_annotation(annotation, *continuation, false)
+ }
+ }
+ }
+ }
+
+ fn format_line(
+ &self,
+ dl: &DisplayLine,
+ lineno_width: usize,
+ inline_marks_width: usize,
+ ) -> String {
+ 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
+ }
+ }
+ }
+ 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)
+ }
+ }
+ DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width),
+ }
+ }
+
+ fn format_inline_marks(
+ &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::<Vec<String>>()
+ .join(""),
+ )
+ }
+}