| 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 | let element_style: DisplayTextStyle = style.unwrap_or(default:DisplayTextStyle::Regular); |
| 57 | result.push(DisplayTextFragment { |
| 58 | content: label, |
| 59 | style: element_style, |
| 60 | }); |
| 61 | } |
| 62 | result |
| 63 | } |
| 64 | |
| 65 | fn format_title(annotation: snippet::Annotation<'_>) -> DisplayLine<'_> { |
| 66 | let label: &str = 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(label:Some(label), style:Some(DisplayTextStyle::Emphasis)), |
| 72 | }, |
| 73 | source_aligned: false, |
| 74 | continuation: false, |
| 75 | }) |
| 76 | } |
| 77 | |
| 78 | fn format_annotation(annotation: snippet::Annotation<'_>) -> Vec<DisplayLine<'_>> { |
| 79 | let mut result: Vec> = vec![]; |
| 80 | let label: &str = annotation.label.unwrap_or_default(); |
| 81 | for (i: usize, line: &str) 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(label:Some(line), style:None), |
| 87 | }, |
| 88 | source_aligned: true, |
| 89 | continuation: i != 0, |
| 90 | })); |
| 91 | } |
| 92 | result |
| 93 | } |
| 94 | |
| 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: Option = slice.annotations.get(index:0).map(|x: &SourceAnnotation<'_>| x.range.0); |
| 102 | let origin: Option<&str> = slice.origin; |
| 103 | let need_empty_header: bool = origin.is_some() || is_first; |
| 104 | let mut body: Vec> = format_body(slice, need_empty_header, has_footer, margin); |
| 105 | let header: Option> = format_header(origin, main_range, &body, is_first); |
| 106 | let mut result: Vec> = vec![]; |
| 107 | |
| 108 | if let Some(header: DisplayLine<'_>) = header { |
| 109 | result.push(header); |
| 110 | } |
| 111 | result.append(&mut body); |
| 112 | result |
| 113 | } |
| 114 | |
| 115 | #[inline ] |
| 116 | // TODO: option_zip |
| 117 | fn zip_opt<A, B>(a: Option<A>, b: Option<B>) -> Option<(A, B)> { |
| 118 | a.and_then(|a: A| b.map(|b: B| (a, b))) |
| 119 | } |
| 120 | |
| 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 | |
| 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 | |
| 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 { |
| 574 | 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 | |