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> { |
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 | |
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 | |
50 | fn 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 | |
76 | fn 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 | |
89 | fn 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 | |
106 | fn 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 |
129 | fn 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 | |
133 | fn 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 | |
181 | fn 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 | |
272 | fn 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 | |
542 | impl<'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 | |
584 | impl 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 | |