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