1 //! Trait for converting `Snippet` to `DisplayList`.
2 use super::*;
3 use crate::{formatter::get_term_style, snippet};
4 
5 struct CursorLines<'a>(&'a str);
6 
7 impl<'a> CursorLines<'a> {
new(src: &str) -> CursorLines<'_>8     fn new(src: &str) -> CursorLines<'_> {
9         CursorLines(src)
10     }
11 }
12 
13 enum EndLine {
14     EOF = 0,
15     CRLF = 1,
16     LF = 2,
17 }
18 
19 impl<'a> Iterator for CursorLines<'a> {
20     type Item = (&'a str, EndLine);
21 
next(&mut self) -> Option<Self::Item>22     fn next(&mut self) -> Option<Self::Item> {
23         if self.0.is_empty() {
24             None
25         } else {
26             self.0
27                 .find('\n')
28                 .map(|x| {
29                     let ret = if 0 < x {
30                         if self.0.as_bytes()[x - 1] == b'\r' {
31                             (&self.0[..x - 1], EndLine::LF)
32                         } else {
33                             (&self.0[..x], EndLine::CRLF)
34                         }
35                     } else {
36                         ("", EndLine::CRLF)
37                     };
38                     self.0 = &self.0[x + 1..];
39                     ret
40                 })
41                 .or_else(|| {
42                     let ret = Some((&self.0[..], EndLine::EOF));
43                     self.0 = "";
44                     ret
45                 })
46         }
47     }
48 }
49 
format_label( label: Option<&str>, style: Option<DisplayTextStyle>, ) -> Vec<DisplayTextFragment<'_>>50 fn format_label(
51     label: Option<&str>,
52     style: Option<DisplayTextStyle>,
53 ) -> Vec<DisplayTextFragment<'_>> {
54     let mut result = vec![];
55     if let Some(label) = label {
56         for (idx, element) in label.split("__").enumerate() {
57             let element_style = match style {
58                 Some(s) => s,
59                 None => {
60                     if idx % 2 == 0 {
61                         DisplayTextStyle::Regular
62                     } else {
63                         DisplayTextStyle::Emphasis
64                     }
65                 }
66             };
67             result.push(DisplayTextFragment {
68                 content: element,
69                 style: element_style,
70             });
71         }
72     }
73     result
74 }
75 
format_title(annotation: snippet::Annotation<'_>) -> DisplayLine<'_>76 fn format_title(annotation: snippet::Annotation<'_>) -> DisplayLine<'_> {
77     let label = annotation.label.unwrap_or_default();
78     DisplayLine::Raw(DisplayRawLine::Annotation {
79         annotation: Annotation {
80             annotation_type: DisplayAnnotationType::from(annotation.annotation_type),
81             id: annotation.id,
82             label: format_label(Some(&label), Some(DisplayTextStyle::Emphasis)),
83         },
84         source_aligned: false,
85         continuation: false,
86     })
87 }
88 
format_annotation(annotation: snippet::Annotation<'_>) -> Vec<DisplayLine<'_>>89 fn format_annotation(annotation: snippet::Annotation<'_>) -> Vec<DisplayLine<'_>> {
90     let mut result = vec![];
91     let label = annotation.label.unwrap_or_default();
92     for (i, line) in label.lines().enumerate() {
93         result.push(DisplayLine::Raw(DisplayRawLine::Annotation {
94             annotation: Annotation {
95                 annotation_type: DisplayAnnotationType::from(annotation.annotation_type),
96                 id: None,
97                 label: format_label(Some(line), None),
98             },
99             source_aligned: true,
100             continuation: i != 0,
101         }));
102     }
103     result
104 }
105 
format_slice( mut slice: snippet::Slice<'_>, is_first: bool, has_footer: bool, ) -> Vec<DisplayLine<'_>>106 fn format_slice(
107     mut slice: snippet::Slice<'_>,
108     is_first: bool,
109     has_footer: bool,
110 ) -> Vec<DisplayLine<'_>> {
111     let main_range = slice.annotations.get(0).map(|x| x.range.0);
112     let row = slice.line_start;
113     let origin = slice.origin.take();
114     let mut body = format_body(slice, has_footer);
115     let header = format_header(origin, main_range, row, &body, is_first);
116     let mut result = vec![];
117 
118     if let Some(header) = header {
119         result.push(header);
120     }
121     result.append(&mut body);
122     result
123 }
124 
format_header<'a>( origin: Option<&'a str>, main_range: Option<usize>, mut row: usize, body: &[DisplayLine<'_>], is_first: bool, ) -> Option<DisplayLine<'a>>125 fn format_header<'a>(
126     origin: Option<&'a str>,
127     main_range: Option<usize>,
128     mut row: usize,
129     body: &[DisplayLine<'_>],
130     is_first: bool,
131 ) -> Option<DisplayLine<'a>> {
132     let display_header = if is_first {
133         DisplayHeaderType::Initial
134     } else {
135         DisplayHeaderType::Continuation
136     };
137 
138     if let Some(main_range) = main_range {
139         let mut col = 1;
140 
141         for item in body {
142             if let DisplayLine::Source {
143                 line: DisplaySourceLine::Content { range, .. },
144                 ..
145             } = item
146             {
147                 if main_range >= range.0 && main_range <= range.1 {
148                     col = main_range - range.0 + 1;
149                     break;
150                 }
151                 row += 1;
152             }
153         }
154         if let Some(path) = origin {
155             return Some(DisplayLine::Raw(DisplayRawLine::Origin {
156                 path,
157                 pos: Some((row, col)),
158                 header_type: display_header,
159             }));
160         }
161     }
162     if let Some(path) = origin {
163         return Some(DisplayLine::Raw(DisplayRawLine::Origin {
164             path,
165             pos: None,
166             header_type: display_header,
167         }));
168     }
169     None
170 }
171 
fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>>172 fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
173     enum Line {
174         Fold(usize),
175         Source(usize),
176     };
177 
178     let mut lines = vec![];
179     let mut no_annotation_lines_counter = 0;
180 
181     for (idx, line) in body.iter().enumerate() {
182         match line {
183             DisplayLine::Source {
184                 line: DisplaySourceLine::Annotation { .. },
185                 ..
186             } => {
187                 if no_annotation_lines_counter > 2 {
188                     let fold_start = idx - no_annotation_lines_counter;
189                     let fold_end = idx;
190                     let pre_len = if no_annotation_lines_counter > 8 {
191                         4
192                     } else {
193                         0
194                     };
195                     let post_len = if no_annotation_lines_counter > 8 {
196                         2
197                     } else {
198                         1
199                     };
200                     for (i, _) in body
201                         .iter()
202                         .enumerate()
203                         .take(fold_start + pre_len)
204                         .skip(fold_start)
205                     {
206                         lines.push(Line::Source(i));
207                     }
208                     lines.push(Line::Fold(idx));
209                     for (i, _) in body
210                         .iter()
211                         .enumerate()
212                         .take(fold_end)
213                         .skip(fold_end - post_len)
214                     {
215                         lines.push(Line::Source(i));
216                     }
217                 } else {
218                     let start = idx - no_annotation_lines_counter;
219                     for (i, _) in body.iter().enumerate().take(idx).skip(start) {
220                         lines.push(Line::Source(i));
221                     }
222                 }
223                 no_annotation_lines_counter = 0;
224             }
225             DisplayLine::Source { .. } => {
226                 no_annotation_lines_counter += 1;
227                 continue;
228             }
229             _ => {
230                 no_annotation_lines_counter += 1;
231             }
232         }
233         lines.push(Line::Source(idx));
234     }
235 
236     let mut new_body = vec![];
237     let mut removed = 0;
238     for line in lines {
239         match line {
240             Line::Source(i) => {
241                 new_body.push(body.remove(i - removed));
242                 removed += 1;
243             }
244             Line::Fold(i) => {
245                 if let DisplayLine::Source {
246                     line: DisplaySourceLine::Annotation { .. },
247                     ref inline_marks,
248                     ..
249                 } = body.get(i - removed).unwrap()
250                 {
251                     new_body.push(DisplayLine::Fold {
252                         inline_marks: inline_marks.clone(),
253                     })
254                 } else {
255                     unreachable!()
256                 }
257             }
258         }
259     }
260 
261     new_body
262 }
263 
format_body(slice: snippet::Slice<'_>, has_footer: bool) -> Vec<DisplayLine<'_>>264 fn format_body(slice: snippet::Slice<'_>, has_footer: bool) -> Vec<DisplayLine<'_>> {
265     let source_len = slice.source.chars().count();
266     if let Some(bigger) = slice.annotations.iter().find_map(|x| {
267         if source_len < x.range.1 {
268             Some(x.range)
269         } else {
270             None
271         }
272     }) {
273         panic!(
274             "SourceAnnotation range `{:?}` is bigger than source length `{}`",
275             bigger, source_len
276         )
277     }
278 
279     let mut body = vec![];
280     let mut current_line = slice.line_start;
281     let mut current_index = 0;
282     let mut line_index_ranges = vec![];
283 
284     for (line, end_line) in CursorLines::new(slice.source) {
285         let line_length = line.chars().count();
286         let line_range = (current_index, current_index + line_length);
287         body.push(DisplayLine::Source {
288             lineno: Some(current_line),
289             inline_marks: vec![],
290             line: DisplaySourceLine::Content {
291                 text: line,
292                 range: line_range,
293             },
294         });
295         line_index_ranges.push(line_range);
296         current_line += 1;
297         current_index += line_length + end_line as usize;
298     }
299 
300     let mut annotation_line_count = 0;
301     let mut annotations = slice.annotations;
302     for (idx, (line_start, line_end)) in line_index_ranges.into_iter().enumerate() {
303         // It would be nice to use filter_drain here once it's stable.
304         annotations = annotations
305             .into_iter()
306             .filter(|annotation| {
307                 let body_idx = idx + annotation_line_count;
308                 let annotation_type = match annotation.annotation_type {
309                     snippet::AnnotationType::Error => DisplayAnnotationType::None,
310                     snippet::AnnotationType::Warning => DisplayAnnotationType::None,
311                     _ => DisplayAnnotationType::from(annotation.annotation_type),
312                 };
313                 match annotation.range {
314                     (start, _) if start > line_end => true,
315                     (start, end)
316                         if start >= line_start && end <= line_end
317                             || start == line_end && end - start <= 1 =>
318                     {
319                         let range = (start - line_start, end - line_start);
320                         body.insert(
321                             body_idx + 1,
322                             DisplayLine::Source {
323                                 lineno: None,
324                                 inline_marks: vec![],
325                                 line: DisplaySourceLine::Annotation {
326                                     annotation: Annotation {
327                                         annotation_type,
328                                         id: None,
329                                         label: format_label(Some(&annotation.label), None),
330                                     },
331                                     range,
332                                     annotation_type: DisplayAnnotationType::from(
333                                         annotation.annotation_type,
334                                     ),
335                                     annotation_part: DisplayAnnotationPart::Standalone,
336                                 },
337                             },
338                         );
339                         annotation_line_count += 1;
340                         false
341                     }
342                     (start, end) if start >= line_start && start <= line_end && end > line_end => {
343                         if start - line_start == 0 {
344                             if let DisplayLine::Source {
345                                 ref mut inline_marks,
346                                 ..
347                             } = body[body_idx]
348                             {
349                                 inline_marks.push(DisplayMark {
350                                     mark_type: DisplayMarkType::AnnotationStart,
351                                     annotation_type: DisplayAnnotationType::from(
352                                         annotation.annotation_type,
353                                     ),
354                                 });
355                             }
356                         } else {
357                             let range = (start - line_start, start - line_start + 1);
358                             body.insert(
359                                 body_idx + 1,
360                                 DisplayLine::Source {
361                                     lineno: None,
362                                     inline_marks: vec![],
363                                     line: DisplaySourceLine::Annotation {
364                                         annotation: Annotation {
365                                             annotation_type: DisplayAnnotationType::None,
366                                             id: None,
367                                             label: vec![],
368                                         },
369                                         range,
370                                         annotation_type: DisplayAnnotationType::from(
371                                             annotation.annotation_type,
372                                         ),
373                                         annotation_part: DisplayAnnotationPart::MultilineStart,
374                                     },
375                                 },
376                             );
377                             annotation_line_count += 1;
378                         }
379                         true
380                     }
381                     (start, end) if start < line_start && end > line_end => {
382                         if let DisplayLine::Source {
383                             ref mut inline_marks,
384                             ..
385                         } = body[body_idx]
386                         {
387                             inline_marks.push(DisplayMark {
388                                 mark_type: DisplayMarkType::AnnotationThrough,
389                                 annotation_type: DisplayAnnotationType::from(
390                                     annotation.annotation_type,
391                                 ),
392                             });
393                         }
394                         true
395                     }
396                     (start, end) if start < line_start && end >= line_start && end <= line_end => {
397                         if let DisplayLine::Source {
398                             ref mut inline_marks,
399                             ..
400                         } = body[body_idx]
401                         {
402                             inline_marks.push(DisplayMark {
403                                 mark_type: DisplayMarkType::AnnotationThrough,
404                                 annotation_type: DisplayAnnotationType::from(
405                                     annotation.annotation_type,
406                                 ),
407                             });
408                         }
409 
410                         let range = (end - line_start, end - line_start + 1);
411                         body.insert(
412                             body_idx + 1,
413                             DisplayLine::Source {
414                                 lineno: None,
415                                 inline_marks: vec![DisplayMark {
416                                     mark_type: DisplayMarkType::AnnotationThrough,
417                                     annotation_type: DisplayAnnotationType::from(
418                                         annotation.annotation_type,
419                                     ),
420                                 }],
421                                 line: DisplaySourceLine::Annotation {
422                                     annotation: Annotation {
423                                         annotation_type,
424                                         id: None,
425                                         label: format_label(Some(&annotation.label), None),
426                                     },
427                                     range,
428                                     annotation_type: DisplayAnnotationType::from(
429                                         annotation.annotation_type,
430                                     ),
431                                     annotation_part: DisplayAnnotationPart::MultilineEnd,
432                                 },
433                             },
434                         );
435                         annotation_line_count += 1;
436                         false
437                     }
438                     _ => true,
439                 }
440             })
441             .collect();
442     }
443 
444     if slice.fold {
445         body = fold_body(body);
446     }
447 
448     body.insert(
449         0,
450         DisplayLine::Source {
451             lineno: None,
452             inline_marks: vec![],
453             line: DisplaySourceLine::Empty,
454         },
455     );
456     if has_footer {
457         body.push(DisplayLine::Source {
458             lineno: None,
459             inline_marks: vec![],
460             line: DisplaySourceLine::Empty,
461         });
462     } else if let Some(DisplayLine::Source { .. }) = body.last() {
463         body.push(DisplayLine::Source {
464             lineno: None,
465             inline_marks: vec![],
466             line: DisplaySourceLine::Empty,
467         });
468     }
469     body
470 }
471 
472 impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> {
473     fn from(
474         snippet::Snippet {
475             title,
476             footer,
477             slices,
478             opt,
479         }: snippet::Snippet<'a>,
480     ) -> DisplayList<'a> {
481         let mut body = vec![];
482         if let Some(annotation) = title {
483             body.push(format_title(annotation));
484         }
485 
486         for (idx, slice) in slices.into_iter().enumerate() {
487             body.append(&mut format_slice(slice, idx == 0, !footer.is_empty()));
488         }
489 
490         for annotation in footer {
491             body.append(&mut format_annotation(annotation));
492         }
493 
494         let FormatOptions {
495             color,
496             anonymized_line_numbers,
497         } = opt;
498 
499         Self {
500             body,
501             stylesheet: get_term_style(color),
502             anonymized_line_numbers,
503         }
504     }
505 }
506 
507 impl From<snippet::AnnotationType> for DisplayAnnotationType {
from(at: snippet::AnnotationType) -> Self508     fn from(at: snippet::AnnotationType) -> Self {
509         match at {
510             snippet::AnnotationType::Error => DisplayAnnotationType::Error,
511             snippet::AnnotationType::Warning => DisplayAnnotationType::Warning,
512             snippet::AnnotationType::Info => DisplayAnnotationType::Info,
513             snippet::AnnotationType::Note => DisplayAnnotationType::Note,
514             snippet::AnnotationType::Help => DisplayAnnotationType::Help,
515         }
516     }
517 }
518