Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/renderer/display/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pub(crate) const ANONYMIZED_LINE_NUM: &str = "LL";
pub(crate) const ERROR_TXT: &str = "error";
pub(crate) const HELP_TXT: &str = "help";
pub(crate) const INFO_TXT: &str = "info";
pub(crate) const NOTE_TXT: &str = "note";
pub(crate) const WARNING_TXT: &str = "warning";
40 changes: 40 additions & 0 deletions src/renderer/display/cursor_line.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use super::end_line::EndLine;

pub(crate) struct CursorLines<'a>(&'a str);

impl CursorLines<'_> {
pub(crate) fn new(src: &str) -> CursorLines<'_> {
CursorLines(src)
}
}

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::Crlf)
} else {
(&self.0[..x], EndLine::Lf)
}
} else {
("", EndLine::Lf)
};
self.0 = &self.0[x + 1..];
ret
})
.or_else(|| {
let ret = Some((self.0, EndLine::Eof));
self.0 = "";
ret
})
}
}
}
137 changes: 137 additions & 0 deletions src/renderer/display/display_annotations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use anstyle::Style;

use crate::{renderer::stylesheet::Stylesheet, snippet};

use super::{constants::*, display_text::DisplayTextFragment};

/// 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(crate) enum DisplayAnnotationType {
None,
Error,
Warning,
Info,
Note,
Help,
}

/// An inline text
/// An indicator of what part of the annotation a given `Annotation` is.
#[derive(Debug, Clone, PartialEq)]
pub(crate) 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(usize),
/// A line ending a multiline annotation.
MultilineEnd(usize),
}

/// Inline annotation which can be used in either Raw or Source line.
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Annotation<'a> {
pub(crate) annotation_type: DisplayAnnotationType,
pub(crate) id: Option<&'a str>,
pub(crate) label: Vec<DisplayTextFragment<'a>>,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) struct DisplaySourceAnnotation<'a> {
pub(crate) annotation: Annotation<'a>,
pub(crate) range: (usize, usize),
pub(crate) annotation_type: DisplayAnnotationType,
pub(crate) annotation_part: DisplayAnnotationPart,
}

impl DisplaySourceAnnotation<'_> {
pub(crate) fn has_label(&self) -> bool {
!self
.annotation
.label
.iter()
.all(|label| label.content.is_empty())
}

// Length of this annotation as displayed in the stderr output
pub(crate) fn len(&self) -> usize {
// Account for usize underflows
if self.range.1 > self.range.0 {
self.range.1 - self.range.0
} else {
self.range.0 - self.range.1
}
}

pub(crate) fn takes_space(&self) -> bool {
// Multiline annotations always have to keep vertical space.
matches!(
self.annotation_part,
DisplayAnnotationPart::MultilineStart(_) | DisplayAnnotationPart::MultilineEnd(_)
)
}
}

impl From<snippet::Level> for DisplayAnnotationType {
fn from(at: snippet::Level) -> Self {
match at {
snippet::Level::Error => DisplayAnnotationType::Error,
snippet::Level::Warning => DisplayAnnotationType::Warning,
snippet::Level::Info => DisplayAnnotationType::Info,
snippet::Level::Note => DisplayAnnotationType::Note,
snippet::Level::Help => DisplayAnnotationType::Help,
}
}
}

#[inline]
pub(crate) fn annotation_type_str(annotation_type: &DisplayAnnotationType) -> &'static str {
match annotation_type {
DisplayAnnotationType::Error => ERROR_TXT,
DisplayAnnotationType::Help => HELP_TXT,
DisplayAnnotationType::Info => INFO_TXT,
DisplayAnnotationType::Note => NOTE_TXT,
DisplayAnnotationType::Warning => WARNING_TXT,
DisplayAnnotationType::None => "",
}
}

pub(crate) fn annotation_type_len(annotation_type: &DisplayAnnotationType) -> usize {
match annotation_type {
DisplayAnnotationType::Error => ERROR_TXT.len(),
DisplayAnnotationType::Help => HELP_TXT.len(),
DisplayAnnotationType::Info => INFO_TXT.len(),
DisplayAnnotationType::Note => NOTE_TXT.len(),
DisplayAnnotationType::Warning => WARNING_TXT.len(),
DisplayAnnotationType::None => 0,
}
}

pub(crate) fn get_annotation_style<'a>(
annotation_type: &DisplayAnnotationType,
stylesheet: &'a Stylesheet,
) -> &'a Style {
match annotation_type {
DisplayAnnotationType::Error => stylesheet.error(),
DisplayAnnotationType::Warning => stylesheet.warning(),
DisplayAnnotationType::Info => stylesheet.info(),
DisplayAnnotationType::Note => stylesheet.note(),
DisplayAnnotationType::Help => stylesheet.help(),
DisplayAnnotationType::None => stylesheet.none(),
}
}

#[inline]
pub(crate) fn is_annotation_empty(annotation: &Annotation<'_>) -> bool {
annotation
.label
.iter()
.all(|fragment| fragment.content.is_empty())
}
11 changes: 11 additions & 0 deletions src/renderer/display/display_header.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/// Information whether the header is the initial one or a consequitive one
/// for multi-slice cases.
// TODO: private
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum DisplayHeaderType {
/// Initial header is the first header in the snippet.
Initial,

/// Continuation marks all headers of following slices in the snippet.
Continuation,
}
64 changes: 64 additions & 0 deletions src/renderer/display/display_line.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use super::{
display_annotations::{Annotation, DisplaySourceAnnotation},
display_header::DisplayHeaderType,
display_mark::DisplayMark,
end_line::EndLine,
};

/// A single line used in `DisplayList`.
#[derive(Debug, PartialEq)]
pub(crate) enum DisplayLine<'a> {
/// A line with `lineno` portion of the slice.
Source {
lineno: Option<usize>,
inline_marks: Vec<DisplayMark>,
line: DisplaySourceLine<'a>,
annotations: Vec<DisplaySourceAnnotation<'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(crate) enum DisplaySourceLine<'a> {
/// A line with the content of the Snippet.
Content {
text: &'a str,
range: (usize, usize), // meta information for annotation placement.
end_line: EndLine,
},
/// 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(crate) 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,
},
}
120 changes: 120 additions & 0 deletions src/renderer/display/display_list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use std::{
cmp,
fmt::{self, Display},
};

use crate::{
renderer::{
display::{
constants::ANONYMIZED_LINE_NUM, display_annotations::DisplayAnnotationPart,
display_line::DisplayLine,
},
styled_buffer::StyledBuffer,
stylesheet::Stylesheet,
},
snippet,
};

use super::{display_set::DisplaySet, format_message};

/// List of lines to be displayed.
pub(crate) struct DisplayList<'a> {
pub(crate) body: Vec<DisplaySet<'a>>,
pub(crate) stylesheet: &'a Stylesheet,
pub(crate) anonymized_line_numbers: bool,
}

impl PartialEq for DisplayList<'_> {
fn eq(&self, other: &Self) -> bool {
self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers
}
}

impl fmt::Debug for DisplayList<'_> {
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 Display for DisplayList<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let lineno_width = self.body.iter().fold(0, |max, set| {
set.display_lines.iter().fold(max, |max, line| match line {
DisplayLine::Source { lineno, .. } => cmp::max(lineno.unwrap_or(0), max),
_ => max,
})
});
let lineno_width = if lineno_width == 0 {
lineno_width
} else if self.anonymized_line_numbers {
ANONYMIZED_LINE_NUM.len()
} else {
((lineno_width as f64).log10().floor() as usize) + 1
};

let multiline_depth = self.body.iter().fold(0, |max, set| {
set.display_lines.iter().fold(max, |max2, line| match line {
DisplayLine::Source { annotations, .. } => cmp::max(
annotations.iter().fold(max2, |max3, line| {
cmp::max(
match line.annotation_part {
DisplayAnnotationPart::Standalone => 0,
DisplayAnnotationPart::LabelContinuation => 0,
DisplayAnnotationPart::MultilineStart(depth) => depth + 1,
DisplayAnnotationPart::MultilineEnd(depth) => depth + 1,
},
max3,
)
}),
max,
),
_ => max2,
})
});
let mut buffer = StyledBuffer::new();
for set in self.body.iter() {
self.format_set(set, lineno_width, multiline_depth, &mut buffer)?;
}
write!(f, "{}", buffer.render(self.stylesheet)?)
}
}

impl<'a> DisplayList<'a> {
pub(crate) fn new(
message: snippet::Message<'a>,
stylesheet: &'a Stylesheet,
anonymized_line_numbers: bool,
term_width: usize,
) -> DisplayList<'a> {
let body = format_message(message, term_width, anonymized_line_numbers, true);

Self {
body,
stylesheet,
anonymized_line_numbers,
}
}

fn format_set(
&self,
set: &DisplaySet<'_>,
lineno_width: usize,
multiline_depth: usize,
buffer: &mut StyledBuffer,
) -> fmt::Result {
for line in &set.display_lines {
set.format_line(
line,
lineno_width,
multiline_depth,
self.stylesheet,
self.anonymized_line_numbers,
buffer,
)?;
}
Ok(())
}
}
15 changes: 15 additions & 0 deletions src/renderer/display/display_mark.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use super::display_annotations::DisplayAnnotationType;

/// A visual mark used in `inline_marks` field of the `DisplaySourceLine`.
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct DisplayMark {
pub(crate) mark_type: DisplayMarkType,
pub(crate) annotation_type: DisplayAnnotationType,
}

/// A type of the `DisplayMark`.
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum DisplayMarkType {
/// A mark indicating a multiline annotation going through the current line.
AnnotationThrough(usize),
}
Loading
Loading