blob: f283e5222ed707fd4a6eb6d84bcf6f19806fc1ab [file] [log] [blame]
//! display_list module stores the output model for the snippet.
//!
//! `DisplayList` is a central structure in the crate, which contains
//! the structured list of lines to be displayed.
//!
//! It is made of two types of lines: `Source` and `Raw`. All `Source` lines
//! are structured using four columns:
//!
//! ```text
//! /------------ (1) Line number column.
//! | /--------- (2) Line number column delimiter.
//! | | /------- (3) Inline marks column.
//! | | | /--- (4) Content column with the source and annotations for slices.
//! | | | |
//! =============================================================================
//! error[E0308]: mismatched types
//! --> src/format.rs:51:5
//! |
//! 151 | / fn test() -> String {
//! 152 | | return "test";
//! 153 | | }
//! | |___^ error: expected `String`, for `&str`.
//! |
//! ```
//!
//! The first two lines of the example above are `Raw` lines, while the rest
//! are `Source` lines.
//!
//! `DisplayList` does not store column alignment information, and those are
//! 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 crate::snippet;
use std::fmt::{Display, Write};
use std::{cmp, fmt};
use crate::renderer::{stylesheet::Stylesheet, Margin, Style};
/// List of lines to be displayed.
pub(crate) struct DisplayList<'a> {
pub body: Vec<DisplayLine<'a>>,
pub stylesheet: &'a Stylesheet,
pub anonymized_line_numbers: bool,
pub margin: Option<Margin>,
}
impl<'a> PartialEq for DisplayList<'a> {
fn eq(&self, other: &Self) -> bool {
self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers
}
}
impl<'a> fmt::Debug for DisplayList<'a> {
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()
}
}
impl<'a> Display for DisplayList<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let lineno_width = self.body.iter().fold(0, |max, line| match line {
DisplayLine::Source {
lineno: Some(lineno),
..
} => {
// The largest line is the largest width.
cmp::max(*lineno, max)
}
_ => max,
});
let lineno_width = if lineno_width == 0 {
lineno_width
} else if self.anonymized_line_numbers {
Self::ANONYMIZED_LINE_NUM.len()
} else {
((lineno_width as f64).log10().floor() as usize) + 1
};
let inline_marks_width = self.body.iter().fold(0, |max, line| match line {
DisplayLine::Source { inline_marks, .. } => cmp::max(inline_marks.len(), max),
_ => max,
});
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<'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,
}: snippet::Snippet<'a>,
stylesheet: &'a Stylesheet,
anonymized_line_numbers: bool,
margin: Option<Margin>,
) -> DisplayList<'a> {
let mut body = vec![];
if let Some(annotation) = title {
body.push(format_title(annotation));
}
for (idx, slice) in slices.into_iter().enumerate() {
body.append(&mut format_slice(
slice,
idx == 0,
!footer.is_empty(),
margin,
));
}
for annotation in footer {
body.append(&mut format_annotation(annotation));
}
Self {
body,
stylesheet,
anonymized_line_numbers,
margin,
}
}
#[inline]
fn format_annotation_type(
annotation_type: &DisplayAnnotationType,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
match annotation_type {
DisplayAnnotationType::Error => f.write_str(Self::ERROR_TXT),
DisplayAnnotationType::Help => f.write_str(Self::HELP_TXT),
DisplayAnnotationType::Info => f.write_str(Self::INFO_TXT),
DisplayAnnotationType::Note => f.write_str(Self::NOTE_TXT),
DisplayAnnotationType::Warning => f.write_str(Self::WARNING_TXT),
DisplayAnnotationType::None => Ok(()),
}
}
fn annotation_type_len(annotation_type: &DisplayAnnotationType) -> usize {
match annotation_type {
DisplayAnnotationType::Error => Self::ERROR_TXT.len(),
DisplayAnnotationType::Help => Self::HELP_TXT.len(),
DisplayAnnotationType::Info => Self::INFO_TXT.len(),
DisplayAnnotationType::Note => Self::NOTE_TXT.len(),
DisplayAnnotationType::Warning => Self::WARNING_TXT.len(),
DisplayAnnotationType::None => 0,
}
}
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(
&self,
label: &[DisplayTextFragment<'_>],
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
let emphasis_style = self.stylesheet.emphasis();
for fragment in label {
match fragment.style {
DisplayTextStyle::Regular => fragment.content.fmt(f)?,
DisplayTextStyle::Emphasis => {
write!(
f,
"{}{}{}",
emphasis_style.render(),
fragment.content,
emphasis_style.render_reset()
)?;
}
}
}
Ok(())
}
fn format_annotation(
&self,
annotation: &Annotation<'_>,
continuation: bool,
in_source: bool,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
let color = self.get_annotation_style(&annotation.annotation_type);
let formatted_len = if let Some(id) = &annotation.id {
2 + id.len() + Self::annotation_type_len(&annotation.annotation_type)
} else {
Self::annotation_type_len(&annotation.annotation_type)
};
if continuation {
format_repeat_char(' ', formatted_len + 2, f)?;
return self.format_label(&annotation.label, f);
}
if formatted_len == 0 {
self.format_label(&annotation.label, f)
} else {
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 {
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)?;
}
}
Ok(())
}
}
#[inline]
fn format_source_line(
&self,
line: &DisplaySourceLine<'_>,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
match line {
DisplaySourceLine::Empty => Ok(()),
DisplaySourceLine::Content { text, .. } => {
f.write_char(' ')?;
if let Some(margin) = self.margin {
let line_len = text.chars().count();
let mut left = margin.left(line_len);
let right = margin.right(line_len);
if margin.was_cut_left() {
// We have stripped some code/whitespace from the beginning, make it clear.
"...".fmt(f)?;
left += 3;
}
// On long lines, we strip the source line, accounting for unicode.
let mut taken = 0;
let cut_right = if margin.was_cut_right(line_len) {
taken += 3;
true
} else {
false
};
// Specifies that it will end on the next character, so it will return
// until the next one to the final condition.
let mut ended = false;
let range = text
.char_indices()
.skip(left)
// Complete char iterator with final character
.chain(std::iter::once((text.len(), '\0')))
// Take until the next one to the final condition
.take_while(|(_, ch)| {
// Fast return to iterate over final byte position
if ended {
return false;
}
// Make sure that the trimming on the right will fall within the terminal width.
// FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` is.
// For now, just accept that sometimes the code line will be longer than desired.
taken += unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1);
if taken > right - left {
ended = true;
}
true
})
// Reduce to start and end byte position
.fold((None, 0), |acc, (i, _)| {
if acc.0.is_some() {
(acc.0, i)
} else {
(Some(i), i)
}
});
// Format text with margins
text[range.0.expect("One character at line")..range.1].fmt(f)?;
if cut_right {
// We have stripped some code after the right-most span end, make it clear we did so.
"...".fmt(f)?;
}
Ok(())
} else {
text.fmt(f)
}
}
DisplaySourceLine::Annotation {
range,
annotation,
annotation_type,
annotation_part,
} => {
let indent_char = match annotation_part {
DisplayAnnotationPart::Standalone => ' ',
DisplayAnnotationPart::LabelContinuation => ' ',
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,
_ => range.0,
};
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(' ')?;
write!(f, "{}", color.render())?;
self.format_annotation(
annotation,
annotation_part == &DisplayAnnotationPart::LabelContinuation,
true,
f,
)?;
write!(f, "{}", color.render_reset())?;
}
Ok(())
}
}
}
#[inline]
fn format_raw_line(
&self,
line: &DisplayRawLine<'_>,
lineno_width: usize,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
match line {
DisplayRawLine::Origin {
path,
pos,
header_type,
} => {
let header_sigil = match header_type {
DisplayHeaderType::Initial => "-->",
DisplayHeaderType::Continuation => ":::",
};
let lineno_color = self.stylesheet.line_no();
if let Some((col, row)) = pos {
format_repeat_char(' ', lineno_width, f)?;
write!(
f,
"{}{}{}",
lineno_color.render(),
header_sigil,
lineno_color.render_reset()
)?;
f.write_char(' ')?;
path.fmt(f)?;
f.write_char(':')?;
col.fmt(f)?;
f.write_char(':')?;
row.fmt(f)
} else {
format_repeat_char(' ', lineno_width, f)?;
write!(
f,
"{}{}{}",
lineno_color.render(),
header_sigil,
lineno_color.render_reset()
)?;
f.write_char(' ')?;
path.fmt(f)
}
}
DisplayRawLine::Annotation {
annotation,
source_aligned,
continuation,
} => {
if *source_aligned {
if *continuation {
format_repeat_char(' ', lineno_width + 3, f)?;
} else {
let lineno_color = self.stylesheet.line_no();
format_repeat_char(' ', lineno_width, f)?;
f.write_char(' ')?;
write!(
f,
"{}={}",
lineno_color.render(),
lineno_color.render_reset()
)?;
f.write_char(' ')?;
}
}
self.format_annotation(annotation, *continuation, false, f)
}
}
}
#[inline]
fn format_line(
&self,
dl: &DisplayLine<'_>,
lineno_width: usize,
inline_marks_width: usize,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
match dl {
DisplayLine::Source {
lineno,
inline_marks,
line,
} => {
let lineno_color = self.stylesheet.line_no();
if self.anonymized_line_numbers && lineno.is_some() {
write!(f, "{}", lineno_color.render())?;
f.write_str(Self::ANONYMIZED_LINE_NUM)?;
f.write_str(" |")?;
write!(f, "{}", lineno_color.render_reset())?;
} else {
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 {
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 } => {
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, f),
}
}
fn format_inline_marks(
&self,
inline_marks: &[DisplayMark],
inline_marks_width: usize,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
format_repeat_char(' ', inline_marks_width - inline_marks.len(), f)?;
for mark in inline_marks {
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(())
}
}
/// Inline annotation which can be used in either Raw or Source line.
#[derive(Debug, PartialEq)]
pub struct Annotation<'a> {
pub annotation_type: DisplayAnnotationType,
pub id: Option<&'a str>,
pub label: Vec<DisplayTextFragment<'a>>,
}
/// A single line used in `DisplayList`.
#[derive(Debug, PartialEq)]
pub enum DisplayLine<'a> {
/// A line with `lineno` portion of the slice.
Source {
lineno: Option<usize>,
inline_marks: Vec<DisplayMark>,
line: DisplaySourceLine<'a>,
},
/// A line indicating a folded part of the slice.
Fold { inline_marks: Vec<DisplayMark> },
/// A line which is displayed outside of slices.
Raw(DisplayRawLine<'a>),
}
/// A source line.
#[derive(Debug, PartialEq)]
pub enum DisplaySourceLine<'a> {
/// A line with the content of the Slice.
Content {
text: &'a str,
range: (usize, usize), // meta information for annotation placement.
},
/// An annotation line which is displayed in context of the slice.
Annotation {
annotation: Annotation<'a>,
range: (usize, usize),
annotation_type: DisplayAnnotationType,
annotation_part: DisplayAnnotationPart,
},
/// An empty source line.
Empty,
}
/// Raw line - a line which does not have the `lineno` part and is not considered
/// a part of the snippet.
#[derive(Debug, PartialEq)]
pub enum DisplayRawLine<'a> {
/// A line which provides information about the location of the given
/// slice in the project structure.
Origin {
path: &'a str,
pos: Option<(usize, usize)>,
header_type: DisplayHeaderType,
},
/// An annotation line which is not part of any snippet.
Annotation {
annotation: Annotation<'a>,
/// If set to `true`, the annotation will be aligned to the
/// lineno delimiter of the snippet.
source_aligned: bool,
/// If set to `true`, only the label of the `Annotation` will be
/// displayed. It allows for a multiline annotation to be aligned
/// without displaying the meta information (`type` and `id`) to be
/// displayed on each line.
continuation: bool,
},
}
/// An inline text fragment which any label is composed of.
#[derive(Debug, PartialEq)]
pub struct DisplayTextFragment<'a> {
pub content: &'a str,
pub style: DisplayTextStyle,
}
/// A style for the `DisplayTextFragment` which can be visually formatted.
///
/// This information may be used to emphasis parts of the label.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DisplayTextStyle {
Regular,
Emphasis,
}
/// An indicator of what part of the annotation a given `Annotation` is.
#[derive(Debug, Clone, PartialEq)]
pub enum DisplayAnnotationPart {
/// A standalone, single-line annotation.
Standalone,
/// A continuation of a multi-line label of an annotation.
LabelContinuation,
/// A line starting a multiline annotation.
MultilineStart,
/// A line ending a multiline annotation.
MultilineEnd,
}
/// A visual mark used in `inline_marks` field of the `DisplaySourceLine`.
#[derive(Debug, Clone, PartialEq)]
pub struct DisplayMark {
pub mark_type: DisplayMarkType,
pub annotation_type: DisplayAnnotationType,
}
/// A type of the `DisplayMark`.
#[derive(Debug, Clone, PartialEq)]
pub enum DisplayMarkType {
/// A mark indicating a multiline annotation going through the current line.
AnnotationThrough,
/// A mark indicating a multiline annotation starting on the given line.
AnnotationStart,
}
/// A type of the `Annotation` which may impact the sigils, style or text displayed.
///
/// There are several ways to uses this information when formatting the `DisplayList`:
///
/// * An annotation may display the name of the type like `error` or `info`.
/// * An underline for `Error` may be `^^^` while for `Warning` it could be `---`.
/// * `ColorStylesheet` may use different colors for different annotations.
#[derive(Debug, Clone, PartialEq)]
pub enum DisplayAnnotationType {
None,
Error,
Warning,
Info,
Note,
Help,
}
impl From<snippet::AnnotationType> for DisplayAnnotationType {
fn from(at: snippet::AnnotationType) -> Self {
match at {
snippet::AnnotationType::Error => DisplayAnnotationType::Error,
snippet::AnnotationType::Warning => DisplayAnnotationType::Warning,
snippet::AnnotationType::Info => DisplayAnnotationType::Info,
snippet::AnnotationType::Note => DisplayAnnotationType::Note,
snippet::AnnotationType::Help => DisplayAnnotationType::Help,
}
}
}
/// 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.
Initial,
/// Continuation marks all headers of following slices in the snippet.
Continuation,
}
struct CursorLines<'a>(&'a str);
impl<'a> CursorLines<'a> {
fn new(src: &str) -> CursorLines<'_> {
CursorLines(src)
}
}
enum EndLine {
Eof = 0,
Crlf = 1,
Lf = 2,
}
impl<'a> Iterator for CursorLines<'a> {
type Item = (&'a str, EndLine);
fn next(&mut self) -> Option<Self::Item> {
if self.0.is_empty() {
None
} else {
self.0
.find('\n')
.map(|x| {
let ret = if 0 < x {
if self.0.as_bytes()[x - 1] == b'\r' {
(&self.0[..x - 1], EndLine::Lf)
} else {
(&self.0[..x], EndLine::Crlf)
}
} else {
("", EndLine::Crlf)
};
self.0 = &self.0[x + 1..];
ret
})
.or_else(|| {
let ret = Some((self.0, EndLine::Eof));
self.0 = "";
ret
})
}
}
}
fn format_label(
label: Option<&str>,
style: Option<DisplayTextStyle>,
) -> Vec<DisplayTextFragment<'_>> {
let mut result = vec![];
if let Some(label) = label {
let element_style = style.unwrap_or(DisplayTextStyle::Regular);
result.push(DisplayTextFragment {
content: label,
style: element_style,
});
}
result
}
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,
label: format_label(Some(label), Some(DisplayTextStyle::Emphasis)),
},
source_aligned: false,
continuation: false,
})
}
fn format_annotation(annotation: snippet::Annotation<'_>) -> Vec<DisplayLine<'_>> {
let mut result = vec![];
let label = annotation.label.unwrap_or_default();
for (i, line) in label.lines().enumerate() {
result.push(DisplayLine::Raw(DisplayRawLine::Annotation {
annotation: Annotation {
annotation_type: DisplayAnnotationType::from(annotation.annotation_type),
id: None,
label: format_label(Some(line), None),
},
source_aligned: true,
continuation: i != 0,
}));
}
result
}
fn format_slice(
slice: snippet::Slice<'_>,
is_first: bool,
has_footer: bool,
margin: Option<Margin>,
) -> Vec<DisplayLine<'_>> {
let main_range = slice.annotations.first().map(|x| x.range.0);
let origin = slice.origin;
let need_empty_header = origin.is_some() || is_first;
let mut body = format_body(slice, need_empty_header, has_footer, margin);
let header = format_header(origin, main_range, &body, is_first);
let mut result = vec![];
if let Some(header) = header {
result.push(header);
}
result.append(&mut body);
result
}
#[inline]
// TODO: option_zip
fn zip_opt<A, B>(a: Option<A>, b: Option<B>) -> Option<(A, B)> {
a.and_then(|a| b.map(|b| (a, b)))
}
fn format_header<'a>(
origin: Option<&'a str>,
main_range: Option<usize>,
body: &[DisplayLine<'_>],
is_first: bool,
) -> Option<DisplayLine<'a>> {
let display_header = if is_first {
DisplayHeaderType::Initial
} else {
DisplayHeaderType::Continuation
};
if let Some((main_range, path)) = zip_opt(main_range, origin) {
let mut col = 1;
let mut line_offset = 1;
for item in body {
if let DisplayLine::Source {
line: DisplaySourceLine::Content { range, .. },
lineno,
..
} = item
{
if main_range >= range.0 && main_range <= range.1 {
col = main_range - range.0 + 1;
line_offset = lineno.unwrap_or(1);
break;
}
}
}
return Some(DisplayLine::Raw(DisplayRawLine::Origin {
path,
pos: Some((line_offset, col)),
header_type: display_header,
}));
}
if let Some(path) = origin {
return Some(DisplayLine::Raw(DisplayRawLine::Origin {
path,
pos: None,
header_type: display_header,
}));
}
None
}
fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
enum Line {
Fold(usize),
Source(usize),
}
let mut lines = vec![];
let mut no_annotation_lines_counter = 0;
for (idx, line) in body.iter().enumerate() {
match line {
DisplayLine::Source {
line: DisplaySourceLine::Annotation { .. },
..
} => {
let fold_start = idx - no_annotation_lines_counter;
if no_annotation_lines_counter > 2 {
let fold_end = idx;
let pre_len = if no_annotation_lines_counter > 8 {
4
} else {
0
};
let post_len = if no_annotation_lines_counter > 8 {
2
} else {
1
};
for (i, _) in body
.iter()
.enumerate()
.take(fold_start + pre_len)
.skip(fold_start)
{
lines.push(Line::Source(i));
}
lines.push(Line::Fold(idx));
for (i, _) in body
.iter()
.enumerate()
.take(fold_end)
.skip(fold_end - post_len)
{
lines.push(Line::Source(i));
}
} else {
for (i, _) in body.iter().enumerate().take(idx).skip(fold_start) {
lines.push(Line::Source(i));
}
}
no_annotation_lines_counter = 0;
}
DisplayLine::Source { .. } => {
no_annotation_lines_counter += 1;
continue;
}
_ => {
no_annotation_lines_counter += 1;
}
}
lines.push(Line::Source(idx));
}
let mut new_body = vec![];
let mut removed = 0;
for line in lines {
match line {
Line::Source(i) => {
new_body.push(body.remove(i - removed));
removed += 1;
}
Line::Fold(i) => {
if let DisplayLine::Source {
line: DisplaySourceLine::Annotation { .. },
ref inline_marks,
..
} = body.get(i - removed).unwrap()
{
new_body.push(DisplayLine::Fold {
inline_marks: inline_marks.clone(),
})
} else {
unreachable!()
}
}
}
}
new_body
}
fn format_body(
slice: snippet::Slice<'_>,
need_empty_header: bool,
has_footer: bool,
margin: Option<Margin>,
) -> Vec<DisplayLine<'_>> {
let source_len = slice.source.chars().count();
if let Some(bigger) = slice.annotations.iter().find_map(|x| {
// Allow highlighting one past the last character in the source.
if source_len + 1 < x.range.1 {
Some(x.range)
} else {
None
}
}) {
panic!(
"SourceAnnotation range `{:?}` is beyond the end of buffer `{}`",
bigger, source_len
)
}
let mut body = vec![];
let mut current_line = slice.line_start;
let mut current_index = 0;
let mut line_info = vec![];
struct LineInfo {
line_start_index: usize,
line_end_index: usize,
// How many spaces each character in the line take up when displayed
char_widths: Vec<usize>,
}
for (line, end_line) in CursorLines::new(slice.source) {
let line_length = line.chars().count();
let line_range = (current_index, current_index + line_length);
let char_widths = line
.chars()
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
.chain(std::iter::once(1)) // treat the end of line as single-width
.collect::<Vec<_>>();
body.push(DisplayLine::Source {
lineno: Some(current_line),
inline_marks: vec![],
line: DisplaySourceLine::Content {
text: line,
range: line_range,
},
});
line_info.push(LineInfo {
line_start_index: line_range.0,
line_end_index: line_range.1,
char_widths,
});
current_line += 1;
current_index += line_length + end_line as usize;
}
let mut annotation_line_count = 0;
let mut annotations = slice.annotations;
for (
idx,
LineInfo {
line_start_index,
line_end_index,
char_widths,
},
) in line_info.into_iter().enumerate()
{
let margin_left = margin
.map(|m| m.left(line_end_index - line_start_index))
.unwrap_or_default();
// It would be nice to use filter_drain here once it's stable.
annotations.retain(|annotation| {
let body_idx = idx + annotation_line_count;
let annotation_type = match annotation.annotation_type {
snippet::AnnotationType::Error => DisplayAnnotationType::None,
snippet::AnnotationType::Warning => DisplayAnnotationType::None,
_ => DisplayAnnotationType::from(annotation.annotation_type),
};
match annotation.range {
(start, _) if start > line_end_index => true,
(start, end)
if start >= line_start_index && end <= line_end_index
|| start == line_end_index && end - start <= 1 =>
{
let annotation_start_col = char_widths
.iter()
.take(start - line_start_index)
.sum::<usize>()
- margin_left;
let annotation_end_col = char_widths
.iter()
.take(end - line_start_index)
.sum::<usize>()
- margin_left;
let range = (annotation_start_col, annotation_end_col);
body.insert(
body_idx + 1,
DisplayLine::Source {
lineno: None,
inline_marks: vec![],
line: DisplaySourceLine::Annotation {
annotation: Annotation {
annotation_type,
id: None,
label: format_label(Some(annotation.label), None),
},
range,
annotation_type: DisplayAnnotationType::from(
annotation.annotation_type,
),
annotation_part: DisplayAnnotationPart::Standalone,
},
},
);
annotation_line_count += 1;
false
}
(start, end)
if start >= line_start_index
&& start <= line_end_index
&& end > line_end_index =>
{
if start - line_start_index == 0 {
if let DisplayLine::Source {
ref mut inline_marks,
..
} = body[body_idx]
{
inline_marks.push(DisplayMark {
mark_type: DisplayMarkType::AnnotationStart,
annotation_type: DisplayAnnotationType::from(
annotation.annotation_type,
),
});
}
} else {
let annotation_start_col = char_widths
.iter()
.take(start - line_start_index)
.sum::<usize>();
let range = (annotation_start_col, annotation_start_col + 1);
body.insert(
body_idx + 1,
DisplayLine::Source {
lineno: None,
inline_marks: vec![],
line: DisplaySourceLine::Annotation {
annotation: Annotation {
annotation_type: DisplayAnnotationType::None,
id: None,
label: vec![],
},
range,
annotation_type: DisplayAnnotationType::from(
annotation.annotation_type,
),
annotation_part: DisplayAnnotationPart::MultilineStart,
},
},
);
annotation_line_count += 1;
}
true
}
(start, end) if start < line_start_index && end > line_end_index => {
if let DisplayLine::Source {
ref mut inline_marks,
..
} = body[body_idx]
{
inline_marks.push(DisplayMark {
mark_type: DisplayMarkType::AnnotationThrough,
annotation_type: DisplayAnnotationType::from(
annotation.annotation_type,
),
});
}
true
}
(start, end)
if start < line_start_index
&& end >= line_start_index
&& end <= line_end_index =>
{
if let DisplayLine::Source {
ref mut inline_marks,
..
} = body[body_idx]
{
inline_marks.push(DisplayMark {
mark_type: DisplayMarkType::AnnotationThrough,
annotation_type: DisplayAnnotationType::from(
annotation.annotation_type,
),
});
}
let end_mark = char_widths
.iter()
.take(end - line_start_index)
.sum::<usize>()
.saturating_sub(1);
let range = (end_mark - margin_left, (end_mark + 1) - margin_left);
body.insert(
body_idx + 1,
DisplayLine::Source {
lineno: None,
inline_marks: vec![DisplayMark {
mark_type: DisplayMarkType::AnnotationThrough,
annotation_type: DisplayAnnotationType::from(
annotation.annotation_type,
),
}],
line: DisplaySourceLine::Annotation {
annotation: Annotation {
annotation_type,
id: None,
label: format_label(Some(annotation.label), None),
},
range,
annotation_type: DisplayAnnotationType::from(
annotation.annotation_type,
),
annotation_part: DisplayAnnotationPart::MultilineEnd,
},
},
);
annotation_line_count += 1;
false
}
_ => true,
}
});
}
if slice.fold {
body = fold_body(body);
}
if need_empty_header {
body.insert(
0,
DisplayLine::Source {
lineno: None,
inline_marks: vec![],
line: DisplaySourceLine::Empty,
},
);
}
if has_footer {
body.push(DisplayLine::Source {
lineno: None,
inline_marks: vec![],
line: DisplaySourceLine::Empty,
});
} else if let Some(DisplayLine::Source { .. }) = body.last() {
body.push(DisplayLine::Source {
lineno: None,
inline_marks: vec![],
line: DisplaySourceLine::Empty,
});
}
body
}
fn format_repeat_char(c: char, n: usize, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for _ in 0..n {
f.write_char(c)?;
}
Ok(())
}
#[inline]
fn is_annotation_empty(annotation: &Annotation<'_>) -> bool {
annotation
.label
.iter()
.all(|fragment| fragment.content.is_empty())
}
#[cfg(test)]
mod tests {
use super::*;
const STYLESHEET: Stylesheet = Stylesheet::plain();
fn from_display_lines(lines: Vec<DisplayLine<'_>>) -> 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() + 2),
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("<current file>"),
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: "<current file>",
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");
}
}