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