blob: faf48f2a1f072f57c1b8f998f72df21dde33c10f [file] [log] [blame]
//! Trait for converting `Snippet` to `DisplayList`.
use super::*;
use crate::{formatter::get_term_style, snippet};
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 {
for (idx, element) in label.split("__").enumerate() {
let element_style = match style {
Some(s) => s,
None => {
if idx % 2 == 0 {
DisplayTextStyle::Regular
} else {
DisplayTextStyle::Emphasis
}
}
};
result.push(DisplayTextFragment {
content: element,
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.get(0).map(|x| x.range.0);
let origin = slice.origin;
let line_start = slice.line_start;
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, line_start, &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>,
mut row: 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;
for item in body {
if let DisplayLine::Source {
line: DisplaySourceLine::Content { range, .. },
..
} = item
{
if main_range >= range.0 && main_range <= range.1 {
col = main_range - range.0 + 1;
break;
}
row += 1;
}
}
return Some(DisplayLine::Raw(DisplayRawLine::Origin {
path,
pos: Some((row, 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| {
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_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 signle-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 = annotations
.into_iter()
.filter(|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,
}
})
.collect();
}
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
}
impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> {
fn from(
snippet::Snippet {
title,
footer,
slices,
opt,
}: snippet::Snippet<'a>,
) -> 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(),
opt.margin,
));
}
for annotation in footer {
body.append(&mut format_annotation(annotation));
}
let FormatOptions {
color,
anonymized_line_numbers,
margin,
} = opt;
Self {
body,
stylesheet: get_term_style(color),
anonymized_line_numbers,
margin,
}
}
}
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,
}
}
}