annotate-snippets 0.0.1

Library for building code annotations
Documentation
use display_list::{DisplayAnnotationType, DisplayLine, DisplayList, DisplayMark};
use std::fmt;

pub struct FormattedDisplayList {
    body: Vec<FormattedDisplayLine>,
}

impl From<DisplayList> for FormattedDisplayList {
    fn from(dl: DisplayList) -> Self {
        let lineno_width = dl.body.iter().fold(0, |max, ref line| match line {
            DisplayLine::Source { lineno, .. } => {
                let width = lineno.to_string().len();
                if width > max {
                    width
                } else {
                    max
                }
            }
            _ => max,
        });
        let inline_marks_width = dl.body.iter().fold(0, |max, ref line| match line {
            DisplayLine::Source { inline_marks, .. } => {
                let width = inline_marks.len();
                if width > max {
                    width + 1
                } else {
                    max
                }
            }
            _ => max,
        });
        let body = dl.body
            .into_iter()
            .map(|line| FormattedDisplayLine::format(line, lineno_width, inline_marks_width))
            .collect();
        FormattedDisplayList { body }
    }
}

impl fmt::Display for FormattedDisplayList {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if let Some((last, elements)) = &self.body.split_last() {
            for line in elements.iter() {
                line.fmt(f)?;
                writeln!(f)?;
            }
            last.fmt(f)?;
        }
        Ok(())
    }
}

#[derive(Debug)]
enum FormattedDisplayLine {
    Raw(String),
    EmptySource {
        lineno: String,
    },
    Source {
        lineno: String,
        inline_marks: String,
        content: String,
    },
    Annotation {
        lineno: String,
        inline_marks: String,
        content: String,
    },
    Fold,
}

impl FormattedDisplayLine {
    fn format(dl: DisplayLine, lineno_width: usize, inline_marks_width: usize) -> Self {
        match dl {
            DisplayLine::Raw(s) => FormattedDisplayLine::Raw(s),
            DisplayLine::EmptySource => FormattedDisplayLine::EmptySource {
                lineno: " ".repeat(lineno_width),
            },
            DisplayLine::Source {
                lineno,
                inline_marks,
                content,
            } => FormattedDisplayLine::Source {
                lineno: format!("{: >width$}", lineno, width = lineno_width),
                inline_marks: Self::format_inline_marks(&inline_marks, inline_marks_width),
                content,
            },
            DisplayLine::Annotation {
                inline_marks,
                range,
                label,
                annotation_type,
            } => FormattedDisplayLine::Annotation {
                lineno: " ".repeat(lineno_width),
                inline_marks: Self::format_inline_marks(&inline_marks, inline_marks_width),
                content: Self::format_annotation_content(range, &label, annotation_type),
            },
            DisplayLine::Fold => FormattedDisplayLine::Fold,
        }
    }

    fn format_inline_marks(inline_marks: &[DisplayMark], inline_marks_width: usize) -> String {
        format!(
            "{: >width$}",
            inline_marks
                .iter()
                .map(|mark| format!("{}", mark))
                .collect::<Vec<String>>()
                .join(""),
            width = inline_marks_width
        )
    }

    fn format_annotation_content(
        range: (usize, usize),
        label: &str,
        annotation_type: DisplayAnnotationType,
    ) -> String {
        match annotation_type {
            DisplayAnnotationType::Error => format!(
                "{}{} {}",
                " ".repeat(range.0),
                "^".repeat(range.1 - range.0),
                label
            ),
            DisplayAnnotationType::Warning => format!(
                "{}{} {}",
                " ".repeat(range.0),
                "-".repeat(range.1 - range.0),
                label
            ),
            DisplayAnnotationType::MultilineStart => format!(
                "{}{} {}",
                "_".repeat(range.0),
                "^".repeat(range.1 - range.0),
                label
            ),
            DisplayAnnotationType::MultilineEnd => format!(
                "{}{} {}",
                "_".repeat(range.0),
                "^".repeat(range.1 - range.0),
                label
            ),
        }
    }
}

impl fmt::Display for FormattedDisplayLine {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            FormattedDisplayLine::EmptySource { lineno } => write!(f, "{} |", lineno),
            FormattedDisplayLine::Source {
                lineno,
                inline_marks,
                content,
            } => write!(f, "{} |{} {}", lineno, inline_marks, content),
            FormattedDisplayLine::Raw(body) => write!(f, "{}", body),
            FormattedDisplayLine::Annotation {
                lineno,
                inline_marks,
                content,
            } => write!(f, "{} |{}{}", lineno, inline_marks, content),
            FormattedDisplayLine::Fold => write!(f, "...  |"),
        }
    }
}