diff options
Diffstat (limited to 'src/display_list/from_snippet.rs')
-rw-r--r-- | src/display_list/from_snippet.rs | 594 |
1 files changed, 594 insertions, 0 deletions
diff --git a/src/display_list/from_snippet.rs b/src/display_list/from_snippet.rs new file mode 100644 index 0000000..faf48f2 --- /dev/null +++ b/src/display_list/from_snippet.rs @@ -0,0 +1,594 @@ +//! 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, + } + } +} |