1use std::cmp::{max, min};
2use std::fmt;
3
4use crate::formatter::{get_term_style, style::Stylesheet};
5
6/// List of lines to be displayed.
7pub struct DisplayList<'a> {
8 pub body: Vec<DisplayLine<'a>>,
9 pub stylesheet: Box<dyn Stylesheet>,
10 pub anonymized_line_numbers: bool,
11 pub margin: Option<Margin>,
12}
13
14impl<'a> From<Vec<DisplayLine<'a>>> for DisplayList<'a> {
15 fn from(body: Vec<DisplayLine<'a>>) -> DisplayList<'a> {
16 Self {
17 body,
18 anonymized_line_numbers: false,
19 stylesheet: get_term_style(color:false),
20 margin: None,
21 }
22 }
23}
24
25impl<'a> PartialEq for DisplayList<'a> {
26 fn eq(&self, other: &Self) -> bool {
27 self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers
28 }
29}
30
31impl<'a> fmt::Debug for DisplayList<'a> {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 f&mut DebugStruct<'_, '_>.debug_struct("DisplayList")
34 .field("body", &self.body)
35 .field(name:"anonymized_line_numbers", &self.anonymized_line_numbers)
36 .finish()
37 }
38}
39
40#[derive(Debug, Default, Copy, Clone)]
41pub struct FormatOptions {
42 pub color: bool,
43 pub anonymized_line_numbers: bool,
44 pub margin: Option<Margin>,
45}
46
47#[derive(Clone, Copy, Debug)]
48pub struct Margin {
49 /// The available whitespace in the left that can be consumed when centering.
50 whitespace_left: usize,
51 /// The column of the beginning of left-most span.
52 span_left: usize,
53 /// The column of the end of right-most span.
54 span_right: usize,
55 /// The beginning of the line to be displayed.
56 computed_left: usize,
57 /// The end of the line to be displayed.
58 computed_right: usize,
59 /// The current width of the terminal. 140 by default and in tests.
60 column_width: usize,
61 /// The end column of a span label, including the span. Doesn't account for labels not in the
62 /// same line as the span.
63 label_right: usize,
64}
65
66impl Margin {
67 pub fn new(
68 whitespace_left: usize,
69 span_left: usize,
70 span_right: usize,
71 label_right: usize,
72 column_width: usize,
73 max_line_len: usize,
74 ) -> Self {
75 // The 6 is padding to give a bit of room for `...` when displaying:
76 // ```
77 // error: message
78 // --> file.rs:16:58
79 // |
80 // 16 | ... fn foo(self) -> Self::Bar {
81 // | ^^^^^^^^^
82 // ```
83
84 let mut m = Margin {
85 whitespace_left: whitespace_left.saturating_sub(6),
86 span_left: span_left.saturating_sub(6),
87 span_right: span_right + 6,
88 computed_left: 0,
89 computed_right: 0,
90 column_width,
91 label_right: label_right + 6,
92 };
93 m.compute(max_line_len);
94 m
95 }
96
97 pub(crate) fn was_cut_left(&self) -> bool {
98 self.computed_left > 0
99 }
100
101 pub(crate) fn was_cut_right(&self, line_len: usize) -> bool {
102 let right =
103 if self.computed_right == self.span_right || self.computed_right == self.label_right {
104 // Account for the "..." padding given above. Otherwise we end up with code lines that
105 // do fit but end in "..." as if they were trimmed.
106 self.computed_right - 6
107 } else {
108 self.computed_right
109 };
110 right < line_len && self.computed_left + self.column_width < line_len
111 }
112
113 fn compute(&mut self, max_line_len: usize) {
114 // When there's a lot of whitespace (>20), we want to trim it as it is useless.
115 self.computed_left = if self.whitespace_left > 20 {
116 self.whitespace_left - 16 // We want some padding.
117 } else {
118 0
119 };
120 // We want to show as much as possible, max_line_len is the right-most boundary for the
121 // relevant code.
122 self.computed_right = max(max_line_len, self.computed_left);
123
124 if self.computed_right - self.computed_left > self.column_width {
125 // Trimming only whitespace isn't enough, let's get craftier.
126 if self.label_right - self.whitespace_left <= self.column_width {
127 // Attempt to fit the code window only trimming whitespace.
128 self.computed_left = self.whitespace_left;
129 self.computed_right = self.computed_left + self.column_width;
130 } else if self.label_right - self.span_left <= self.column_width {
131 // Attempt to fit the code window considering only the spans and labels.
132 let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2;
133 self.computed_left = self.span_left.saturating_sub(padding_left);
134 self.computed_right = self.computed_left + self.column_width;
135 } else if self.span_right - self.span_left <= self.column_width {
136 // Attempt to fit the code window considering the spans and labels plus padding.
137 let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2;
138 self.computed_left = self.span_left.saturating_sub(padding_left);
139 self.computed_right = self.computed_left + self.column_width;
140 } else {
141 // Mostly give up but still don't show the full line.
142 self.computed_left = self.span_left;
143 self.computed_right = self.span_right;
144 }
145 }
146 }
147
148 pub(crate) fn left(&self, line_len: usize) -> usize {
149 min(self.computed_left, line_len)
150 }
151
152 pub(crate) fn right(&self, line_len: usize) -> usize {
153 if line_len.saturating_sub(self.computed_left) <= self.column_width {
154 line_len
155 } else {
156 min(line_len, self.computed_right)
157 }
158 }
159}
160
161/// Inline annotation which can be used in either Raw or Source line.
162#[derive(Debug, PartialEq)]
163pub struct Annotation<'a> {
164 pub annotation_type: DisplayAnnotationType,
165 pub id: Option<&'a str>,
166 pub label: Vec<DisplayTextFragment<'a>>,
167}
168
169/// A single line used in `DisplayList`.
170#[derive(Debug, PartialEq)]
171pub enum DisplayLine<'a> {
172 /// A line with `lineno` portion of the slice.
173 Source {
174 lineno: Option<usize>,
175 inline_marks: Vec<DisplayMark>,
176 line: DisplaySourceLine<'a>,
177 },
178
179 /// A line indicating a folded part of the slice.
180 Fold { inline_marks: Vec<DisplayMark> },
181
182 /// A line which is displayed outside of slices.
183 Raw(DisplayRawLine<'a>),
184}
185
186/// A source line.
187#[derive(Debug, PartialEq)]
188pub enum DisplaySourceLine<'a> {
189 /// A line with the content of the Slice.
190 Content {
191 text: &'a str,
192 range: (usize, usize), // meta information for annotation placement.
193 },
194
195 /// An annotation line which is displayed in context of the slice.
196 Annotation {
197 annotation: Annotation<'a>,
198 range: (usize, usize),
199 annotation_type: DisplayAnnotationType,
200 annotation_part: DisplayAnnotationPart,
201 },
202
203 /// An empty source line.
204 Empty,
205}
206
207/// Raw line - a line which does not have the `lineno` part and is not considered
208/// a part of the snippet.
209#[derive(Debug, PartialEq)]
210pub enum DisplayRawLine<'a> {
211 /// A line which provides information about the location of the given
212 /// slice in the project structure.
213 Origin {
214 path: &'a str,
215 pos: Option<(usize, usize)>,
216 header_type: DisplayHeaderType,
217 },
218
219 /// An annotation line which is not part of any snippet.
220 Annotation {
221 annotation: Annotation<'a>,
222
223 /// If set to `true`, the annotation will be aligned to the
224 /// lineno delimiter of the snippet.
225 source_aligned: bool,
226 /// If set to `true`, only the label of the `Annotation` will be
227 /// displayed. It allows for a multiline annotation to be aligned
228 /// without displaing the meta information (`type` and `id`) to be
229 /// displayed on each line.
230 continuation: bool,
231 },
232}
233
234/// An inline text fragment which any label is composed of.
235#[derive(Debug, PartialEq)]
236pub struct DisplayTextFragment<'a> {
237 pub content: &'a str,
238 pub style: DisplayTextStyle,
239}
240
241/// A style for the `DisplayTextFragment` which can be visually formatted.
242///
243/// This information may be used to emphasis parts of the label.
244#[derive(Debug, Clone, Copy, PartialEq)]
245pub enum DisplayTextStyle {
246 Regular,
247 Emphasis,
248}
249
250/// An indicator of what part of the annotation a given `Annotation` is.
251#[derive(Debug, Clone, PartialEq)]
252pub enum DisplayAnnotationPart {
253 /// A standalone, single-line annotation.
254 Standalone,
255 /// A continuation of a multi-line label of an annotation.
256 LabelContinuation,
257 /// A consequitive annotation in case multiple annotations annotate a single line.
258 Consequitive,
259 /// A line starting a multiline annotation.
260 MultilineStart,
261 /// A line ending a multiline annotation.
262 MultilineEnd,
263}
264
265/// A visual mark used in `inline_marks` field of the `DisplaySourceLine`.
266#[derive(Debug, Clone, PartialEq)]
267pub struct DisplayMark {
268 pub mark_type: DisplayMarkType,
269 pub annotation_type: DisplayAnnotationType,
270}
271
272/// A type of the `DisplayMark`.
273#[derive(Debug, Clone, PartialEq)]
274pub enum DisplayMarkType {
275 /// A mark indicating a multiline annotation going through the current line.
276 AnnotationThrough,
277 /// A mark indicating a multiline annotation starting on the given line.
278 AnnotationStart,
279}
280
281/// A type of the `Annotation` which may impact the sigils, style or text displayed.
282///
283/// There are several ways to uses this information when formatting the `DisplayList`:
284///
285/// * An annotation may display the name of the type like `error` or `info`.
286/// * An underline for `Error` may be `^^^` while for `Warning` it coule be `---`.
287/// * `ColorStylesheet` may use different colors for different annotations.
288#[derive(Debug, Clone, PartialEq)]
289pub enum DisplayAnnotationType {
290 None,
291 Error,
292 Warning,
293 Info,
294 Note,
295 Help,
296}
297
298/// Information whether the header is the initial one or a consequitive one
299/// for multi-slice cases.
300// TODO: private
301#[derive(Debug, Clone, PartialEq)]
302pub enum DisplayHeaderType {
303 /// Initial header is the first header in the snippet.
304 Initial,
305
306 /// Continuation marks all headers of following slices in the snippet.
307 Continuation,
308}
309