Skip to content

Commit 34a69b4

Browse files
committed
refactor(display-list): split into separate modules (#184)
1 parent 5c6ce17 commit 34a69b4

13 files changed

+1800
-1725
lines changed

src/renderer/display/constants.rs

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pub(crate) const ANONYMIZED_LINE_NUM: &str = "LL";
2+
pub(crate) const ERROR_TXT: &str = "error";
3+
pub(crate) const HELP_TXT: &str = "help";
4+
pub(crate) const INFO_TXT: &str = "info";
5+
pub(crate) const NOTE_TXT: &str = "note";
6+
pub(crate) const WARNING_TXT: &str = "warning";

src/renderer/display/cursor_line.rs

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use super::end_line::EndLine;
2+
3+
pub(crate) struct CursorLines<'a>(&'a str);
4+
5+
impl CursorLines<'_> {
6+
pub(crate) fn new(src: &str) -> CursorLines<'_> {
7+
CursorLines(src)
8+
}
9+
}
10+
11+
impl<'a> Iterator for CursorLines<'a> {
12+
type Item = (&'a str, EndLine);
13+
14+
fn next(&mut self) -> Option<Self::Item> {
15+
if self.0.is_empty() {
16+
None
17+
} else {
18+
self.0
19+
.find('\n')
20+
.map(|x| {
21+
let ret = if 0 < x {
22+
if self.0.as_bytes()[x - 1] == b'\r' {
23+
(&self.0[..x - 1], EndLine::Crlf)
24+
} else {
25+
(&self.0[..x], EndLine::Lf)
26+
}
27+
} else {
28+
("", EndLine::Lf)
29+
};
30+
self.0 = &self.0[x + 1..];
31+
ret
32+
})
33+
.or_else(|| {
34+
let ret = Some((self.0, EndLine::Eof));
35+
self.0 = "";
36+
ret
37+
})
38+
}
39+
}
40+
}
+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
use anstyle::Style;
2+
3+
use crate::{renderer::stylesheet::Stylesheet, snippet};
4+
5+
use super::{constants::*, display_text::DisplayTextFragment};
6+
7+
/// A type of the `Annotation` which may impact the sigils, style or text displayed.
8+
///
9+
/// There are several ways to uses this information when formatting the `DisplayList`:
10+
///
11+
/// * An annotation may display the name of the type like `error` or `info`.
12+
/// * An underline for `Error` may be `^^^` while for `Warning` it could be `---`.
13+
/// * `ColorStylesheet` may use different colors for different annotations.
14+
#[derive(Debug, Clone, PartialEq)]
15+
pub(crate) enum DisplayAnnotationType {
16+
None,
17+
Error,
18+
Warning,
19+
Info,
20+
Note,
21+
Help,
22+
}
23+
24+
/// An inline text
25+
/// An indicator of what part of the annotation a given `Annotation` is.
26+
#[derive(Debug, Clone, PartialEq)]
27+
pub(crate) enum DisplayAnnotationPart {
28+
/// A standalone, single-line annotation.
29+
Standalone,
30+
/// A continuation of a multi-line label of an annotation.
31+
LabelContinuation,
32+
/// A line starting a multiline annotation.
33+
MultilineStart(usize),
34+
/// A line ending a multiline annotation.
35+
MultilineEnd(usize),
36+
}
37+
38+
/// Inline annotation which can be used in either Raw or Source line.
39+
#[derive(Clone, Debug, PartialEq)]
40+
pub(crate) struct Annotation<'a> {
41+
pub(crate) annotation_type: DisplayAnnotationType,
42+
pub(crate) id: Option<&'a str>,
43+
pub(crate) label: Vec<DisplayTextFragment<'a>>,
44+
}
45+
46+
#[derive(Clone, Debug, PartialEq)]
47+
pub(crate) struct DisplaySourceAnnotation<'a> {
48+
pub(crate) annotation: Annotation<'a>,
49+
pub(crate) range: (usize, usize),
50+
pub(crate) annotation_type: DisplayAnnotationType,
51+
pub(crate) annotation_part: DisplayAnnotationPart,
52+
}
53+
54+
impl DisplaySourceAnnotation<'_> {
55+
pub(crate) fn has_label(&self) -> bool {
56+
!self
57+
.annotation
58+
.label
59+
.iter()
60+
.all(|label| label.content.is_empty())
61+
}
62+
63+
// Length of this annotation as displayed in the stderr output
64+
pub(crate) fn len(&self) -> usize {
65+
// Account for usize underflows
66+
if self.range.1 > self.range.0 {
67+
self.range.1 - self.range.0
68+
} else {
69+
self.range.0 - self.range.1
70+
}
71+
}
72+
73+
pub(crate) fn takes_space(&self) -> bool {
74+
// Multiline annotations always have to keep vertical space.
75+
matches!(
76+
self.annotation_part,
77+
DisplayAnnotationPart::MultilineStart(_) | DisplayAnnotationPart::MultilineEnd(_)
78+
)
79+
}
80+
}
81+
82+
impl From<snippet::Level> for DisplayAnnotationType {
83+
fn from(at: snippet::Level) -> Self {
84+
match at {
85+
snippet::Level::Error => DisplayAnnotationType::Error,
86+
snippet::Level::Warning => DisplayAnnotationType::Warning,
87+
snippet::Level::Info => DisplayAnnotationType::Info,
88+
snippet::Level::Note => DisplayAnnotationType::Note,
89+
snippet::Level::Help => DisplayAnnotationType::Help,
90+
}
91+
}
92+
}
93+
94+
#[inline]
95+
pub(crate) fn annotation_type_str(annotation_type: &DisplayAnnotationType) -> &'static str {
96+
match annotation_type {
97+
DisplayAnnotationType::Error => ERROR_TXT,
98+
DisplayAnnotationType::Help => HELP_TXT,
99+
DisplayAnnotationType::Info => INFO_TXT,
100+
DisplayAnnotationType::Note => NOTE_TXT,
101+
DisplayAnnotationType::Warning => WARNING_TXT,
102+
DisplayAnnotationType::None => "",
103+
}
104+
}
105+
106+
pub(crate) fn annotation_type_len(annotation_type: &DisplayAnnotationType) -> usize {
107+
match annotation_type {
108+
DisplayAnnotationType::Error => ERROR_TXT.len(),
109+
DisplayAnnotationType::Help => HELP_TXT.len(),
110+
DisplayAnnotationType::Info => INFO_TXT.len(),
111+
DisplayAnnotationType::Note => NOTE_TXT.len(),
112+
DisplayAnnotationType::Warning => WARNING_TXT.len(),
113+
DisplayAnnotationType::None => 0,
114+
}
115+
}
116+
117+
pub(crate) fn get_annotation_style<'a>(
118+
annotation_type: &DisplayAnnotationType,
119+
stylesheet: &'a Stylesheet,
120+
) -> &'a Style {
121+
match annotation_type {
122+
DisplayAnnotationType::Error => stylesheet.error(),
123+
DisplayAnnotationType::Warning => stylesheet.warning(),
124+
DisplayAnnotationType::Info => stylesheet.info(),
125+
DisplayAnnotationType::Note => stylesheet.note(),
126+
DisplayAnnotationType::Help => stylesheet.help(),
127+
DisplayAnnotationType::None => stylesheet.none(),
128+
}
129+
}
130+
131+
#[inline]
132+
pub(crate) fn is_annotation_empty(annotation: &Annotation<'_>) -> bool {
133+
annotation
134+
.label
135+
.iter()
136+
.all(|fragment| fragment.content.is_empty())
137+
}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// Information whether the header is the initial one or a consequitive one
2+
/// for multi-slice cases.
3+
// TODO: private
4+
#[derive(Debug, Clone, PartialEq)]
5+
pub(crate) enum DisplayHeaderType {
6+
/// Initial header is the first header in the snippet.
7+
Initial,
8+
9+
/// Continuation marks all headers of following slices in the snippet.
10+
Continuation,
11+
}

src/renderer/display/display_line.rs

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use super::{
2+
display_annotations::{Annotation, DisplaySourceAnnotation},
3+
display_header::DisplayHeaderType,
4+
display_mark::DisplayMark,
5+
end_line::EndLine,
6+
};
7+
8+
/// A single line used in `DisplayList`.
9+
#[derive(Debug, PartialEq)]
10+
pub(crate) enum DisplayLine<'a> {
11+
/// A line with `lineno` portion of the slice.
12+
Source {
13+
lineno: Option<usize>,
14+
inline_marks: Vec<DisplayMark>,
15+
line: DisplaySourceLine<'a>,
16+
annotations: Vec<DisplaySourceAnnotation<'a>>,
17+
},
18+
19+
/// A line indicating a folded part of the slice.
20+
Fold { inline_marks: Vec<DisplayMark> },
21+
22+
/// A line which is displayed outside of slices.
23+
Raw(DisplayRawLine<'a>),
24+
}
25+
26+
/// A source line.
27+
#[derive(Debug, PartialEq)]
28+
pub(crate) enum DisplaySourceLine<'a> {
29+
/// A line with the content of the Snippet.
30+
Content {
31+
text: &'a str,
32+
range: (usize, usize), // meta information for annotation placement.
33+
end_line: EndLine,
34+
},
35+
/// An empty source line.
36+
Empty,
37+
}
38+
39+
/// Raw line - a line which does not have the `lineno` part and is not considered
40+
/// a part of the snippet.
41+
#[derive(Debug, PartialEq)]
42+
pub(crate) enum DisplayRawLine<'a> {
43+
/// A line which provides information about the location of the given
44+
/// slice in the project structure.
45+
Origin {
46+
path: &'a str,
47+
pos: Option<(usize, usize)>,
48+
header_type: DisplayHeaderType,
49+
},
50+
51+
/// An annotation line which is not part of any snippet.
52+
Annotation {
53+
annotation: Annotation<'a>,
54+
55+
/// If set to `true`, the annotation will be aligned to the
56+
/// lineno delimiter of the snippet.
57+
source_aligned: bool,
58+
/// If set to `true`, only the label of the `Annotation` will be
59+
/// displayed. It allows for a multiline annotation to be aligned
60+
/// without displaying the meta information (`type` and `id`) to be
61+
/// displayed on each line.
62+
continuation: bool,
63+
},
64+
}

src/renderer/display/display_list.rs

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use std::{
2+
cmp,
3+
fmt::{self, Display},
4+
};
5+
6+
use crate::{
7+
renderer::{
8+
display::{
9+
constants::ANONYMIZED_LINE_NUM, display_annotations::DisplayAnnotationPart,
10+
display_line::DisplayLine,
11+
},
12+
styled_buffer::StyledBuffer,
13+
stylesheet::Stylesheet,
14+
},
15+
snippet,
16+
};
17+
18+
use super::{display_set::DisplaySet, format_message};
19+
20+
/// List of lines to be displayed.
21+
pub(crate) struct DisplayList<'a> {
22+
pub(crate) body: Vec<DisplaySet<'a>>,
23+
pub(crate) stylesheet: &'a Stylesheet,
24+
pub(crate) anonymized_line_numbers: bool,
25+
}
26+
27+
impl PartialEq for DisplayList<'_> {
28+
fn eq(&self, other: &Self) -> bool {
29+
self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers
30+
}
31+
}
32+
33+
impl fmt::Debug for DisplayList<'_> {
34+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35+
f.debug_struct("DisplayList")
36+
.field("body", &self.body)
37+
.field("anonymized_line_numbers", &self.anonymized_line_numbers)
38+
.finish()
39+
}
40+
}
41+
42+
impl Display for DisplayList<'_> {
43+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44+
let lineno_width = self.body.iter().fold(0, |max, set| {
45+
set.display_lines.iter().fold(max, |max, line| match line {
46+
DisplayLine::Source { lineno, .. } => cmp::max(lineno.unwrap_or(0), max),
47+
_ => max,
48+
})
49+
});
50+
let lineno_width = if lineno_width == 0 {
51+
lineno_width
52+
} else if self.anonymized_line_numbers {
53+
ANONYMIZED_LINE_NUM.len()
54+
} else {
55+
((lineno_width as f64).log10().floor() as usize) + 1
56+
};
57+
58+
let multiline_depth = self.body.iter().fold(0, |max, set| {
59+
set.display_lines.iter().fold(max, |max2, line| match line {
60+
DisplayLine::Source { annotations, .. } => cmp::max(
61+
annotations.iter().fold(max2, |max3, line| {
62+
cmp::max(
63+
match line.annotation_part {
64+
DisplayAnnotationPart::Standalone => 0,
65+
DisplayAnnotationPart::LabelContinuation => 0,
66+
DisplayAnnotationPart::MultilineStart(depth) => depth + 1,
67+
DisplayAnnotationPart::MultilineEnd(depth) => depth + 1,
68+
},
69+
max3,
70+
)
71+
}),
72+
max,
73+
),
74+
_ => max2,
75+
})
76+
});
77+
let mut buffer = StyledBuffer::new();
78+
for set in self.body.iter() {
79+
self.format_set(set, lineno_width, multiline_depth, &mut buffer)?;
80+
}
81+
write!(f, "{}", buffer.render(self.stylesheet)?)
82+
}
83+
}
84+
85+
impl<'a> DisplayList<'a> {
86+
pub(crate) fn new(
87+
message: snippet::Message<'a>,
88+
stylesheet: &'a Stylesheet,
89+
anonymized_line_numbers: bool,
90+
term_width: usize,
91+
) -> DisplayList<'a> {
92+
let body = format_message(message, term_width, anonymized_line_numbers, true);
93+
94+
Self {
95+
body,
96+
stylesheet,
97+
anonymized_line_numbers,
98+
}
99+
}
100+
101+
fn format_set(
102+
&self,
103+
set: &DisplaySet<'_>,
104+
lineno_width: usize,
105+
multiline_depth: usize,
106+
buffer: &mut StyledBuffer,
107+
) -> fmt::Result {
108+
for line in &set.display_lines {
109+
set.format_line(
110+
line,
111+
lineno_width,
112+
multiline_depth,
113+
self.stylesheet,
114+
self.anonymized_line_numbers,
115+
buffer,
116+
)?;
117+
}
118+
Ok(())
119+
}
120+
}

src/renderer/display/display_mark.rs

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use super::display_annotations::DisplayAnnotationType;
2+
3+
/// A visual mark used in `inline_marks` field of the `DisplaySourceLine`.
4+
#[derive(Debug, Clone, PartialEq)]
5+
pub(crate) struct DisplayMark {
6+
pub(crate) mark_type: DisplayMarkType,
7+
pub(crate) annotation_type: DisplayAnnotationType,
8+
}
9+
10+
/// A type of the `DisplayMark`.
11+
#[derive(Debug, Clone, PartialEq)]
12+
pub(crate) enum DisplayMarkType {
13+
/// A mark indicating a multiline annotation going through the current line.
14+
AnnotationThrough(usize),
15+
}

0 commit comments

Comments
 (0)