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