• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         let element_style = style.unwrap_or(DisplayTextStyle::Regular);
57         result.push(DisplayTextFragment {
58             content: label,
59             style: element_style,
60         });
61     }
62     result
63 }
64 
format_title(annotation: snippet::Annotation<'_>) -> DisplayLine<'_>65 fn format_title(annotation: snippet::Annotation<'_>) -> DisplayLine<'_> {
66     let label = annotation.label.unwrap_or_default();
67     DisplayLine::Raw(DisplayRawLine::Annotation {
68         annotation: Annotation {
69             annotation_type: DisplayAnnotationType::from(annotation.annotation_type),
70             id: annotation.id,
71             label: format_label(Some(label), Some(DisplayTextStyle::Emphasis)),
72         },
73         source_aligned: false,
74         continuation: false,
75     })
76 }
77 
format_annotation(annotation: snippet::Annotation<'_>) -> Vec<DisplayLine<'_>>78 fn format_annotation(annotation: snippet::Annotation<'_>) -> Vec<DisplayLine<'_>> {
79     let mut result = vec![];
80     let label = annotation.label.unwrap_or_default();
81     for (i, line) in label.lines().enumerate() {
82         result.push(DisplayLine::Raw(DisplayRawLine::Annotation {
83             annotation: Annotation {
84                 annotation_type: DisplayAnnotationType::from(annotation.annotation_type),
85                 id: None,
86                 label: format_label(Some(line), None),
87             },
88             source_aligned: true,
89             continuation: i != 0,
90         }));
91     }
92     result
93 }
94 
format_slice( slice: snippet::Slice<'_>, is_first: bool, has_footer: bool, margin: Option<Margin>, ) -> Vec<DisplayLine<'_>>95 fn format_slice(
96     slice: snippet::Slice<'_>,
97     is_first: bool,
98     has_footer: bool,
99     margin: Option<Margin>,
100 ) -> Vec<DisplayLine<'_>> {
101     let main_range = slice.annotations.get(0).map(|x| x.range.0);
102     let origin = slice.origin;
103     let need_empty_header = origin.is_some() || is_first;
104     let mut body = format_body(slice, need_empty_header, has_footer, margin);
105     let header = format_header(origin, main_range, &body, is_first);
106     let mut result = vec![];
107 
108     if let Some(header) = header {
109         result.push(header);
110     }
111     result.append(&mut body);
112     result
113 }
114 
115 #[inline]
116 // TODO: option_zip
zip_opt<A, B>(a: Option<A>, b: Option<B>) -> Option<(A, B)>117 fn zip_opt<A, B>(a: Option<A>, b: Option<B>) -> Option<(A, B)> {
118     a.and_then(|a| b.map(|b| (a, b)))
119 }
120 
format_header<'a>( origin: Option<&'a str>, main_range: Option<usize>, body: &[DisplayLine<'_>], is_first: bool, ) -> Option<DisplayLine<'a>>121 fn format_header<'a>(
122     origin: Option<&'a str>,
123     main_range: Option<usize>,
124     body: &[DisplayLine<'_>],
125     is_first: bool,
126 ) -> Option<DisplayLine<'a>> {
127     let display_header = if is_first {
128         DisplayHeaderType::Initial
129     } else {
130         DisplayHeaderType::Continuation
131     };
132 
133     if let Some((main_range, path)) = zip_opt(main_range, origin) {
134         let mut col = 1;
135         let mut line_offset = 1;
136 
137         for item in body {
138             if let DisplayLine::Source {
139                 line: DisplaySourceLine::Content { range, .. },
140                 lineno,
141                 ..
142             } = item
143             {
144                 if main_range >= range.0 && main_range <= range.1 {
145                     col = main_range - range.0 + 1;
146                     line_offset = lineno.unwrap_or(1);
147                     break;
148                 }
149             }
150         }
151 
152         return Some(DisplayLine::Raw(DisplayRawLine::Origin {
153             path,
154             pos: Some((line_offset, col)),
155             header_type: display_header,
156         }));
157     }
158 
159     if let Some(path) = origin {
160         return Some(DisplayLine::Raw(DisplayRawLine::Origin {
161             path,
162             pos: None,
163             header_type: display_header,
164         }));
165     }
166 
167     None
168 }
169 
fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>>170 fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> {
171     enum Line {
172         Fold(usize),
173         Source(usize),
174     }
175 
176     let mut lines = vec![];
177     let mut no_annotation_lines_counter = 0;
178 
179     for (idx, line) in body.iter().enumerate() {
180         match line {
181             DisplayLine::Source {
182                 line: DisplaySourceLine::Annotation { .. },
183                 ..
184             } => {
185                 let fold_start = idx - no_annotation_lines_counter;
186                 if no_annotation_lines_counter > 2 {
187                     let fold_end = idx;
188                     let pre_len = if no_annotation_lines_counter > 8 {
189                         4
190                     } else {
191                         0
192                     };
193                     let post_len = if no_annotation_lines_counter > 8 {
194                         2
195                     } else {
196                         1
197                     };
198                     for (i, _) in body
199                         .iter()
200                         .enumerate()
201                         .take(fold_start + pre_len)
202                         .skip(fold_start)
203                     {
204                         lines.push(Line::Source(i));
205                     }
206                     lines.push(Line::Fold(idx));
207                     for (i, _) in body
208                         .iter()
209                         .enumerate()
210                         .take(fold_end)
211                         .skip(fold_end - post_len)
212                     {
213                         lines.push(Line::Source(i));
214                     }
215                 } else {
216                     for (i, _) in body.iter().enumerate().take(idx).skip(fold_start) {
217                         lines.push(Line::Source(i));
218                     }
219                 }
220                 no_annotation_lines_counter = 0;
221             }
222             DisplayLine::Source { .. } => {
223                 no_annotation_lines_counter += 1;
224                 continue;
225             }
226             _ => {
227                 no_annotation_lines_counter += 1;
228             }
229         }
230         lines.push(Line::Source(idx));
231     }
232 
233     let mut new_body = vec![];
234     let mut removed = 0;
235     for line in lines {
236         match line {
237             Line::Source(i) => {
238                 new_body.push(body.remove(i - removed));
239                 removed += 1;
240             }
241             Line::Fold(i) => {
242                 if let DisplayLine::Source {
243                     line: DisplaySourceLine::Annotation { .. },
244                     ref inline_marks,
245                     ..
246                 } = body.get(i - removed).unwrap()
247                 {
248                     new_body.push(DisplayLine::Fold {
249                         inline_marks: inline_marks.clone(),
250                     })
251                 } else {
252                     unreachable!()
253                 }
254             }
255         }
256     }
257 
258     new_body
259 }
260 
format_body( slice: snippet::Slice<'_>, need_empty_header: bool, has_footer: bool, margin: Option<Margin>, ) -> Vec<DisplayLine<'_>>261 fn format_body(
262     slice: snippet::Slice<'_>,
263     need_empty_header: bool,
264     has_footer: bool,
265     margin: Option<Margin>,
266 ) -> Vec<DisplayLine<'_>> {
267     let source_len = slice.source.chars().count();
268     if let Some(bigger) = slice.annotations.iter().find_map(|x| {
269         if source_len < x.range.1 {
270             Some(x.range)
271         } else {
272             None
273         }
274     }) {
275         panic!(
276             "SourceAnnotation range `{:?}` is bigger than source length `{}`",
277             bigger, source_len
278         )
279     }
280 
281     let mut body = vec![];
282     let mut current_line = slice.line_start;
283     let mut current_index = 0;
284     let mut line_info = vec![];
285 
286     struct LineInfo {
287         line_start_index: usize,
288         line_end_index: usize,
289         // How many spaces each character in the line take up when displayed
290         char_widths: Vec<usize>,
291     }
292 
293     for (line, end_line) in CursorLines::new(slice.source) {
294         let line_length = line.chars().count();
295         let line_range = (current_index, current_index + line_length);
296         let char_widths = line
297             .chars()
298             .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
299             .chain(std::iter::once(1)) // treat the end of line as single-width
300             .collect::<Vec<_>>();
301         body.push(DisplayLine::Source {
302             lineno: Some(current_line),
303             inline_marks: vec![],
304             line: DisplaySourceLine::Content {
305                 text: line,
306                 range: line_range,
307             },
308         });
309         line_info.push(LineInfo {
310             line_start_index: line_range.0,
311             line_end_index: line_range.1,
312             char_widths,
313         });
314         current_line += 1;
315         current_index += line_length + end_line as usize;
316     }
317 
318     let mut annotation_line_count = 0;
319     let mut annotations = slice.annotations;
320     for (
321         idx,
322         LineInfo {
323             line_start_index,
324             line_end_index,
325             char_widths,
326         },
327     ) in line_info.into_iter().enumerate()
328     {
329         let margin_left = margin
330             .map(|m| m.left(line_end_index - line_start_index))
331             .unwrap_or_default();
332         // It would be nice to use filter_drain here once it's stable.
333         annotations = annotations
334             .into_iter()
335             .filter(|annotation| {
336                 let body_idx = idx + annotation_line_count;
337                 let annotation_type = match annotation.annotation_type {
338                     snippet::AnnotationType::Error => DisplayAnnotationType::None,
339                     snippet::AnnotationType::Warning => DisplayAnnotationType::None,
340                     _ => DisplayAnnotationType::from(annotation.annotation_type),
341                 };
342                 match annotation.range {
343                     (start, _) if start > line_end_index => true,
344                     (start, end)
345                         if start >= line_start_index && end <= line_end_index
346                             || start == line_end_index && end - start <= 1 =>
347                     {
348                         let annotation_start_col = char_widths
349                             .iter()
350                             .take(start - line_start_index)
351                             .sum::<usize>()
352                             - margin_left;
353                         let annotation_end_col = char_widths
354                             .iter()
355                             .take(end - line_start_index)
356                             .sum::<usize>()
357                             - margin_left;
358                         let range = (annotation_start_col, annotation_end_col);
359                         body.insert(
360                             body_idx + 1,
361                             DisplayLine::Source {
362                                 lineno: None,
363                                 inline_marks: vec![],
364                                 line: DisplaySourceLine::Annotation {
365                                     annotation: Annotation {
366                                         annotation_type,
367                                         id: None,
368                                         label: format_label(Some(annotation.label), None),
369                                     },
370                                     range,
371                                     annotation_type: DisplayAnnotationType::from(
372                                         annotation.annotation_type,
373                                     ),
374                                     annotation_part: DisplayAnnotationPart::Standalone,
375                                 },
376                             },
377                         );
378                         annotation_line_count += 1;
379                         false
380                     }
381                     (start, end)
382                         if start >= line_start_index
383                             && start <= line_end_index
384                             && end > line_end_index =>
385                     {
386                         if start - line_start_index == 0 {
387                             if let DisplayLine::Source {
388                                 ref mut inline_marks,
389                                 ..
390                             } = body[body_idx]
391                             {
392                                 inline_marks.push(DisplayMark {
393                                     mark_type: DisplayMarkType::AnnotationStart,
394                                     annotation_type: DisplayAnnotationType::from(
395                                         annotation.annotation_type,
396                                     ),
397                                 });
398                             }
399                         } else {
400                             let annotation_start_col = char_widths
401                                 .iter()
402                                 .take(start - line_start_index)
403                                 .sum::<usize>();
404                             let range = (annotation_start_col, annotation_start_col + 1);
405                             body.insert(
406                                 body_idx + 1,
407                                 DisplayLine::Source {
408                                     lineno: None,
409                                     inline_marks: vec![],
410                                     line: DisplaySourceLine::Annotation {
411                                         annotation: Annotation {
412                                             annotation_type: DisplayAnnotationType::None,
413                                             id: None,
414                                             label: vec![],
415                                         },
416                                         range,
417                                         annotation_type: DisplayAnnotationType::from(
418                                             annotation.annotation_type,
419                                         ),
420                                         annotation_part: DisplayAnnotationPart::MultilineStart,
421                                     },
422                                 },
423                             );
424                             annotation_line_count += 1;
425                         }
426                         true
427                     }
428                     (start, end) if start < line_start_index && end > line_end_index => {
429                         if let DisplayLine::Source {
430                             ref mut inline_marks,
431                             ..
432                         } = body[body_idx]
433                         {
434                             inline_marks.push(DisplayMark {
435                                 mark_type: DisplayMarkType::AnnotationThrough,
436                                 annotation_type: DisplayAnnotationType::from(
437                                     annotation.annotation_type,
438                                 ),
439                             });
440                         }
441                         true
442                     }
443                     (start, end)
444                         if start < line_start_index
445                             && end >= line_start_index
446                             && end <= line_end_index =>
447                     {
448                         if let DisplayLine::Source {
449                             ref mut inline_marks,
450                             ..
451                         } = body[body_idx]
452                         {
453                             inline_marks.push(DisplayMark {
454                                 mark_type: DisplayMarkType::AnnotationThrough,
455                                 annotation_type: DisplayAnnotationType::from(
456                                     annotation.annotation_type,
457                                 ),
458                             });
459                         }
460 
461                         let end_mark = char_widths
462                             .iter()
463                             .take(end - line_start_index)
464                             .sum::<usize>()
465                             .saturating_sub(1);
466                         let range = (end_mark - margin_left, (end_mark + 1) - margin_left);
467                         body.insert(
468                             body_idx + 1,
469                             DisplayLine::Source {
470                                 lineno: None,
471                                 inline_marks: vec![DisplayMark {
472                                     mark_type: DisplayMarkType::AnnotationThrough,
473                                     annotation_type: DisplayAnnotationType::from(
474                                         annotation.annotation_type,
475                                     ),
476                                 }],
477                                 line: DisplaySourceLine::Annotation {
478                                     annotation: Annotation {
479                                         annotation_type,
480                                         id: None,
481                                         label: format_label(Some(annotation.label), None),
482                                     },
483                                     range,
484                                     annotation_type: DisplayAnnotationType::from(
485                                         annotation.annotation_type,
486                                     ),
487                                     annotation_part: DisplayAnnotationPart::MultilineEnd,
488                                 },
489                             },
490                         );
491                         annotation_line_count += 1;
492                         false
493                     }
494                     _ => true,
495                 }
496             })
497             .collect();
498     }
499 
500     if slice.fold {
501         body = fold_body(body);
502     }
503 
504     if need_empty_header {
505         body.insert(
506             0,
507             DisplayLine::Source {
508                 lineno: None,
509                 inline_marks: vec![],
510                 line: DisplaySourceLine::Empty,
511             },
512         );
513     }
514 
515     if has_footer {
516         body.push(DisplayLine::Source {
517             lineno: None,
518             inline_marks: vec![],
519             line: DisplaySourceLine::Empty,
520         });
521     } else if let Some(DisplayLine::Source { .. }) = body.last() {
522         body.push(DisplayLine::Source {
523             lineno: None,
524             inline_marks: vec![],
525             line: DisplaySourceLine::Empty,
526         });
527     }
528     body
529 }
530 
531 impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> {
532     fn from(
533         snippet::Snippet {
534             title,
535             footer,
536             slices,
537             opt,
538         }: snippet::Snippet<'a>,
539     ) -> DisplayList<'a> {
540         let mut body = vec![];
541         if let Some(annotation) = title {
542             body.push(format_title(annotation));
543         }
544 
545         for (idx, slice) in slices.into_iter().enumerate() {
546             body.append(&mut format_slice(
547                 slice,
548                 idx == 0,
549                 !footer.is_empty(),
550                 opt.margin,
551             ));
552         }
553 
554         for annotation in footer {
555             body.append(&mut format_annotation(annotation));
556         }
557 
558         let FormatOptions {
559             color,
560             anonymized_line_numbers,
561             margin,
562         } = opt;
563 
564         Self {
565             body,
566             stylesheet: get_term_style(color),
567             anonymized_line_numbers,
568             margin,
569         }
570     }
571 }
572 
573 impl From<snippet::AnnotationType> for DisplayAnnotationType {
from(at: snippet::AnnotationType) -> Self574     fn from(at: snippet::AnnotationType) -> Self {
575         match at {
576             snippet::AnnotationType::Error => DisplayAnnotationType::Error,
577             snippet::AnnotationType::Warning => DisplayAnnotationType::Warning,
578             snippet::AnnotationType::Info => DisplayAnnotationType::Info,
579             snippet::AnnotationType::Note => DisplayAnnotationType::Note,
580             snippet::AnnotationType::Help => DisplayAnnotationType::Help,
581         }
582     }
583 }
584