1 | use std::{ |
2 | cmp, |
3 | fmt::{self, Display, Write}, |
4 | iter::once, |
5 | }; |
6 | |
7 | pub mod style; |
8 | |
9 | use self::style::{Style, StyleClass, Stylesheet}; |
10 | |
11 | #[cfg (feature = "color" )] |
12 | use crate::stylesheets::color::AnsiTermStylesheet; |
13 | use crate::{display_list::*, stylesheets::no_color::NoColorStylesheet}; |
14 | |
15 | fn format_repeat_char(c: char, n: usize, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
16 | for _ in 0..n { |
17 | f.write_char(c)?; |
18 | } |
19 | Ok(()) |
20 | } |
21 | |
22 | #[inline ] |
23 | fn is_annotation_empty(annotation: &Annotation<'_>) -> bool { |
24 | annotationIter<'_, DisplayTextFragment<'_>> |
25 | .label |
26 | .iter() |
27 | .all(|fragment: &DisplayTextFragment<'_>| fragment.content.is_empty()) |
28 | } |
29 | |
30 | #[cfg (feature = "color" )] |
31 | #[inline ] |
32 | pub fn get_term_style(color: bool) -> Box<dyn Stylesheet> { |
33 | if color { |
34 | Box::new(AnsiTermStylesheet) |
35 | } else { |
36 | Box::new(NoColorStylesheet) |
37 | } |
38 | } |
39 | |
40 | #[cfg (not(feature = "color" ))] |
41 | #[inline ] |
42 | pub fn get_term_style(_color: bool) -> Box<dyn Stylesheet> { |
43 | Box::new(NoColorStylesheet) |
44 | } |
45 | |
46 | impl<'a> fmt::Display for DisplayList<'a> { |
47 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
48 | let lineno_width = self.body.iter().fold(0, |max, line| match line { |
49 | DisplayLine::Source { |
50 | lineno: Some(lineno), |
51 | .. |
52 | } => { |
53 | // The largest line is the largest width. |
54 | cmp::max(*lineno, max) |
55 | } |
56 | _ => max, |
57 | }); |
58 | let lineno_width = if lineno_width == 0 { |
59 | lineno_width |
60 | } else if self.anonymized_line_numbers { |
61 | Self::ANONYMIZED_LINE_NUM.len() |
62 | } else { |
63 | ((lineno_width as f64).log10().floor() as usize) + 1 |
64 | }; |
65 | let inline_marks_width = self.body.iter().fold(0, |max, line| match line { |
66 | DisplayLine::Source { inline_marks, .. } => cmp::max(inline_marks.len(), max), |
67 | _ => max, |
68 | }); |
69 | |
70 | for (i, line) in self.body.iter().enumerate() { |
71 | self.format_line(line, lineno_width, inline_marks_width, f)?; |
72 | if i + 1 < self.body.len() { |
73 | f.write_char(' \n' )?; |
74 | } |
75 | } |
76 | Ok(()) |
77 | } |
78 | } |
79 | |
80 | impl<'a> DisplayList<'a> { |
81 | const ANONYMIZED_LINE_NUM: &'static str = "LL" ; |
82 | const ERROR_TXT: &'static str = "error" ; |
83 | const HELP_TXT: &'static str = "help" ; |
84 | const INFO_TXT: &'static str = "info" ; |
85 | const NOTE_TXT: &'static str = "note" ; |
86 | const WARNING_TXT: &'static str = "warning" ; |
87 | |
88 | #[inline ] |
89 | fn format_annotation_type( |
90 | annotation_type: &DisplayAnnotationType, |
91 | f: &mut fmt::Formatter<'_>, |
92 | ) -> fmt::Result { |
93 | match annotation_type { |
94 | DisplayAnnotationType::Error => f.write_str(Self::ERROR_TXT), |
95 | DisplayAnnotationType::Help => f.write_str(Self::HELP_TXT), |
96 | DisplayAnnotationType::Info => f.write_str(Self::INFO_TXT), |
97 | DisplayAnnotationType::Note => f.write_str(Self::NOTE_TXT), |
98 | DisplayAnnotationType::Warning => f.write_str(Self::WARNING_TXT), |
99 | DisplayAnnotationType::None => Ok(()), |
100 | } |
101 | } |
102 | |
103 | fn annotation_type_len(annotation_type: &DisplayAnnotationType) -> usize { |
104 | match annotation_type { |
105 | DisplayAnnotationType::Error => Self::ERROR_TXT.len(), |
106 | DisplayAnnotationType::Help => Self::HELP_TXT.len(), |
107 | DisplayAnnotationType::Info => Self::INFO_TXT.len(), |
108 | DisplayAnnotationType::Note => Self::NOTE_TXT.len(), |
109 | DisplayAnnotationType::Warning => Self::WARNING_TXT.len(), |
110 | DisplayAnnotationType::None => 0, |
111 | } |
112 | } |
113 | |
114 | fn get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> Box<dyn Style> { |
115 | self.stylesheet.get_style(match annotation_type { |
116 | DisplayAnnotationType::Error => StyleClass::Error, |
117 | DisplayAnnotationType::Warning => StyleClass::Warning, |
118 | DisplayAnnotationType::Info => StyleClass::Info, |
119 | DisplayAnnotationType::Note => StyleClass::Note, |
120 | DisplayAnnotationType::Help => StyleClass::Help, |
121 | DisplayAnnotationType::None => StyleClass::None, |
122 | }) |
123 | } |
124 | |
125 | fn format_label( |
126 | &self, |
127 | label: &[DisplayTextFragment<'_>], |
128 | f: &mut fmt::Formatter<'_>, |
129 | ) -> fmt::Result { |
130 | let emphasis_style = self.stylesheet.get_style(StyleClass::Emphasis); |
131 | |
132 | for fragment in label { |
133 | match fragment.style { |
134 | DisplayTextStyle::Regular => fragment.content.fmt(f)?, |
135 | DisplayTextStyle::Emphasis => emphasis_style.paint(fragment.content, f)?, |
136 | } |
137 | } |
138 | Ok(()) |
139 | } |
140 | |
141 | fn format_annotation( |
142 | &self, |
143 | annotation: &Annotation<'_>, |
144 | continuation: bool, |
145 | in_source: bool, |
146 | f: &mut fmt::Formatter<'_>, |
147 | ) -> fmt::Result { |
148 | let color = self.get_annotation_style(&annotation.annotation_type); |
149 | let formatted_len = if let Some(id) = &annotation.id { |
150 | 2 + id.len() + Self::annotation_type_len(&annotation.annotation_type) |
151 | } else { |
152 | Self::annotation_type_len(&annotation.annotation_type) |
153 | }; |
154 | |
155 | if continuation { |
156 | format_repeat_char(' ' , formatted_len + 2, f)?; |
157 | return self.format_label(&annotation.label, f); |
158 | } |
159 | if formatted_len == 0 { |
160 | self.format_label(&annotation.label, f) |
161 | } else { |
162 | color.paint_fn( |
163 | Box::new(|f| { |
164 | Self::format_annotation_type(&annotation.annotation_type, f)?; |
165 | if let Some(id) = &annotation.id { |
166 | f.write_char('[' )?; |
167 | f.write_str(id)?; |
168 | f.write_char(']' )?; |
169 | } |
170 | Ok(()) |
171 | }), |
172 | f, |
173 | )?; |
174 | if !is_annotation_empty(annotation) { |
175 | if in_source { |
176 | color.paint_fn( |
177 | Box::new(|f| { |
178 | f.write_str(": " )?; |
179 | self.format_label(&annotation.label, f) |
180 | }), |
181 | f, |
182 | )?; |
183 | } else { |
184 | f.write_str(": " )?; |
185 | self.format_label(&annotation.label, f)?; |
186 | } |
187 | } |
188 | Ok(()) |
189 | } |
190 | } |
191 | |
192 | #[inline ] |
193 | fn format_source_line( |
194 | &self, |
195 | line: &DisplaySourceLine<'_>, |
196 | f: &mut fmt::Formatter<'_>, |
197 | ) -> fmt::Result { |
198 | match line { |
199 | DisplaySourceLine::Empty => Ok(()), |
200 | DisplaySourceLine::Content { text, .. } => { |
201 | f.write_char(' ' )?; |
202 | if let Some(margin) = self.margin { |
203 | let line_len = text.chars().count(); |
204 | let mut left = margin.left(line_len); |
205 | let right = margin.right(line_len); |
206 | |
207 | if margin.was_cut_left() { |
208 | // We have stripped some code/whitespace from the beginning, make it clear. |
209 | "..." .fmt(f)?; |
210 | left += 3; |
211 | } |
212 | |
213 | // On long lines, we strip the source line, accounting for unicode. |
214 | let mut taken = 0; |
215 | let cut_right = if margin.was_cut_right(line_len) { |
216 | taken += 3; |
217 | true |
218 | } else { |
219 | false |
220 | }; |
221 | // Specifies that it will end on the next character, so it will return |
222 | // until the next one to the final condition. |
223 | let mut ended = false; |
224 | let range = text |
225 | .char_indices() |
226 | .skip(left) |
227 | // Complete char iterator with final character |
228 | .chain(once((text.len(), ' \0' ))) |
229 | // Take until the next one to the final condition |
230 | .take_while(|(_, ch)| { |
231 | // Fast return to iterate over final byte position |
232 | if ended { |
233 | return false; |
234 | } |
235 | // Make sure that the trimming on the right will fall within the terminal width. |
236 | // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` is. |
237 | // For now, just accept that sometimes the code line will be longer than desired. |
238 | taken += unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1); |
239 | if taken > right - left { |
240 | ended = true; |
241 | } |
242 | true |
243 | }) |
244 | // Reduce to start and end byte position |
245 | .fold((None, 0), |acc, (i, _)| { |
246 | if acc.0.is_some() { |
247 | (acc.0, i) |
248 | } else { |
249 | (Some(i), i) |
250 | } |
251 | }); |
252 | |
253 | // Format text with margins |
254 | text[range.0.expect("One character at line" )..range.1].fmt(f)?; |
255 | |
256 | if cut_right { |
257 | // We have stripped some code after the right-most span end, make it clear we did so. |
258 | "..." .fmt(f)?; |
259 | } |
260 | Ok(()) |
261 | } else { |
262 | text.fmt(f) |
263 | } |
264 | } |
265 | DisplaySourceLine::Annotation { |
266 | range, |
267 | annotation, |
268 | annotation_type, |
269 | annotation_part, |
270 | } => { |
271 | let indent_char = match annotation_part { |
272 | DisplayAnnotationPart::Standalone => ' ' , |
273 | DisplayAnnotationPart::LabelContinuation => ' ' , |
274 | DisplayAnnotationPart::Consequitive => ' ' , |
275 | DisplayAnnotationPart::MultilineStart => '_' , |
276 | DisplayAnnotationPart::MultilineEnd => '_' , |
277 | }; |
278 | let mark = match annotation_type { |
279 | DisplayAnnotationType::Error => '^' , |
280 | DisplayAnnotationType::Warning => '-' , |
281 | DisplayAnnotationType::Info => '-' , |
282 | DisplayAnnotationType::Note => '-' , |
283 | DisplayAnnotationType::Help => '-' , |
284 | DisplayAnnotationType::None => ' ' , |
285 | }; |
286 | let color = self.get_annotation_style(annotation_type); |
287 | let indent_length = match annotation_part { |
288 | DisplayAnnotationPart::LabelContinuation => range.1, |
289 | DisplayAnnotationPart::Consequitive => range.1, |
290 | _ => range.0, |
291 | }; |
292 | |
293 | color.paint_fn( |
294 | Box::new(|f| { |
295 | format_repeat_char(indent_char, indent_length + 1, f)?; |
296 | format_repeat_char(mark, range.1 - indent_length, f) |
297 | }), |
298 | f, |
299 | )?; |
300 | |
301 | if !is_annotation_empty(annotation) { |
302 | f.write_char(' ' )?; |
303 | color.paint_fn( |
304 | Box::new(|f| { |
305 | self.format_annotation( |
306 | annotation, |
307 | annotation_part == &DisplayAnnotationPart::LabelContinuation, |
308 | true, |
309 | f, |
310 | ) |
311 | }), |
312 | f, |
313 | )?; |
314 | } |
315 | |
316 | Ok(()) |
317 | } |
318 | } |
319 | } |
320 | |
321 | #[inline ] |
322 | fn format_raw_line( |
323 | &self, |
324 | line: &DisplayRawLine<'_>, |
325 | lineno_width: usize, |
326 | f: &mut fmt::Formatter<'_>, |
327 | ) -> fmt::Result { |
328 | match line { |
329 | DisplayRawLine::Origin { |
330 | path, |
331 | pos, |
332 | header_type, |
333 | } => { |
334 | let header_sigil = match header_type { |
335 | DisplayHeaderType::Initial => "-->" , |
336 | DisplayHeaderType::Continuation => ":::" , |
337 | }; |
338 | let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); |
339 | |
340 | if let Some((col, row)) = pos { |
341 | format_repeat_char(' ' , lineno_width, f)?; |
342 | lineno_color.paint(header_sigil, f)?; |
343 | f.write_char(' ' )?; |
344 | path.fmt(f)?; |
345 | f.write_char(':' )?; |
346 | col.fmt(f)?; |
347 | f.write_char(':' )?; |
348 | row.fmt(f) |
349 | } else { |
350 | format_repeat_char(' ' , lineno_width, f)?; |
351 | lineno_color.paint(header_sigil, f)?; |
352 | f.write_char(' ' )?; |
353 | path.fmt(f) |
354 | } |
355 | } |
356 | DisplayRawLine::Annotation { |
357 | annotation, |
358 | source_aligned, |
359 | continuation, |
360 | } => { |
361 | if *source_aligned { |
362 | if *continuation { |
363 | format_repeat_char(' ' , lineno_width + 3, f)?; |
364 | } else { |
365 | let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); |
366 | format_repeat_char(' ' , lineno_width, f)?; |
367 | f.write_char(' ' )?; |
368 | lineno_color.paint("=" , f)?; |
369 | f.write_char(' ' )?; |
370 | } |
371 | } |
372 | self.format_annotation(annotation, *continuation, false, f) |
373 | } |
374 | } |
375 | } |
376 | |
377 | #[inline ] |
378 | fn format_line( |
379 | &self, |
380 | dl: &DisplayLine<'_>, |
381 | lineno_width: usize, |
382 | inline_marks_width: usize, |
383 | f: &mut fmt::Formatter<'_>, |
384 | ) -> fmt::Result { |
385 | match dl { |
386 | DisplayLine::Source { |
387 | lineno, |
388 | inline_marks, |
389 | line, |
390 | } => { |
391 | let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); |
392 | if self.anonymized_line_numbers && lineno.is_some() { |
393 | lineno_color.paint_fn( |
394 | Box::new(|f| { |
395 | f.write_str(Self::ANONYMIZED_LINE_NUM)?; |
396 | f.write_str(" |" ) |
397 | }), |
398 | f, |
399 | )?; |
400 | } else { |
401 | lineno_color.paint_fn( |
402 | Box::new(|f| { |
403 | match lineno { |
404 | Some(n) => write!(f, " {:>width$}" , n, width = lineno_width), |
405 | None => format_repeat_char(' ' , lineno_width, f), |
406 | }?; |
407 | f.write_str(" |" ) |
408 | }), |
409 | f, |
410 | )?; |
411 | } |
412 | if *line != DisplaySourceLine::Empty { |
413 | if !inline_marks.is_empty() || 0 < inline_marks_width { |
414 | f.write_char(' ' )?; |
415 | self.format_inline_marks(inline_marks, inline_marks_width, f)?; |
416 | } |
417 | self.format_source_line(line, f)?; |
418 | } else if !inline_marks.is_empty() { |
419 | f.write_char(' ' )?; |
420 | self.format_inline_marks(inline_marks, inline_marks_width, f)?; |
421 | } |
422 | Ok(()) |
423 | } |
424 | DisplayLine::Fold { inline_marks } => { |
425 | f.write_str("..." )?; |
426 | if !inline_marks.is_empty() || 0 < inline_marks_width { |
427 | format_repeat_char(' ' , lineno_width, f)?; |
428 | self.format_inline_marks(inline_marks, inline_marks_width, f)?; |
429 | } |
430 | Ok(()) |
431 | } |
432 | DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width, f), |
433 | } |
434 | } |
435 | |
436 | fn format_inline_marks( |
437 | &self, |
438 | inline_marks: &[DisplayMark], |
439 | inline_marks_width: usize, |
440 | f: &mut fmt::Formatter<'_>, |
441 | ) -> fmt::Result { |
442 | format_repeat_char(' ' , inline_marks_width - inline_marks.len(), f)?; |
443 | for mark in inline_marks { |
444 | self.get_annotation_style(&mark.annotation_type).paint_fn( |
445 | Box::new(|f| { |
446 | f.write_char(match mark.mark_type { |
447 | DisplayMarkType::AnnotationThrough => '|' , |
448 | DisplayMarkType::AnnotationStart => '/' , |
449 | }) |
450 | }), |
451 | f, |
452 | )?; |
453 | } |
454 | Ok(()) |
455 | } |
456 | } |
457 | |