Skip to content

Commit 025bb74

Browse files
committed
refactor: display-list (#184)
1 parent 5c6ce17 commit 025bb74

12 files changed

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

0 commit comments

Comments
 (0)