1use std::borrow::Borrow;
2use std::io;
3use std::ops::Range;
4
5use super::draw::{self, StreamAwareFmt, StreamType};
6use super::{Cache, CharSet, Label, LabelAttach, Report, ReportKind, Show, Span, Write};
7
8enum LabelKind {
17 Inline,
18 Multiline,
19}
20
21struct LabelInfo<'a, S> {
22 kind: LabelKind,
23 label: &'a Label<S>,
24}
25
26struct SourceGroup<'a, S: Span> {
27 src_id: &'a S::SourceId,
28 span: Range<usize>,
29 labels: Vec<LabelInfo<'a, S>>,
30}
31
32impl<S: Span> Report<'_, S> {
33 fn get_source_groups(&self, cache: &mut impl Cache<S::SourceId>) -> Vec<SourceGroup<S>> {
34 let mut groups = Vec::new();
35 for label in self.labels.iter() {
36 let src_display = cache.display(label.span.source());
37 let src = match cache.fetch(label.span.source()) {
38 Ok(src) => src,
39 Err(e) => {
40 eprintln!("Unable to fetch source '{}': {:?}", Show(src_display), e);
41 continue;
42 }
43 };
44
45 assert!(
46 label.span.start() <= label.span.end(),
47 "Label start is after its end"
48 );
49
50 let start_line = src.get_offset_line(label.span.start()).map(|(_, l, _)| l);
51 let end_line = src
52 .get_offset_line(label.span.end().saturating_sub(1).max(label.span.start()))
53 .map(|(_, l, _)| l);
54
55 let label_info = LabelInfo {
56 kind: if start_line == end_line {
57 LabelKind::Inline
58 } else {
59 LabelKind::Multiline
60 },
61 label,
62 };
63
64 if let Some(group) = groups
65 .iter_mut()
66 .find(|g: &&mut SourceGroup<S>| g.src_id == label.span.source())
67 {
68 group.span.start = group.span.start.min(label.span.start());
69 group.span.end = group.span.end.max(label.span.end());
70 group.labels.push(label_info);
71 } else {
72 groups.push(SourceGroup {
73 src_id: label.span.source(),
74 span: label.span.start()..label.span.end(),
75 labels: vec![label_info],
76 });
77 }
78 }
79 groups
80 }
81
82 pub fn write<C: Cache<S::SourceId>, W: Write>(&self, cache: C, w: W) -> io::Result<()> {
89 self.write_for_stream(cache, w, StreamType::Stderr)
90 }
91
92 pub fn write_for_stdout<C: Cache<S::SourceId>, W: Write>(
95 &self,
96 cache: C,
97 w: W,
98 ) -> io::Result<()> {
99 self.write_for_stream(cache, w, StreamType::Stdout)
100 }
101
102 fn write_for_stream<C: Cache<S::SourceId>, W: Write>(
105 &self,
106 mut cache: C,
107 mut w: W,
108 s: StreamType,
109 ) -> io::Result<()> {
110 let draw = match self.config.char_set {
111 CharSet::Unicode => draw::Characters::unicode(),
112 CharSet::Ascii => draw::Characters::ascii(),
113 };
114
115 let code = self.code.as_ref().map(|c| format!("[{}] ", c));
118 let id = format!("{}{}:", Show(code), self.kind);
119 let kind_color = match self.kind {
120 ReportKind::Error => self.config.error_color(),
121 ReportKind::Warning => self.config.warning_color(),
122 ReportKind::Advice => self.config.advice_color(),
123 ReportKind::Custom(_, color) => Some(color),
124 };
125 writeln!(w, "{} {}", id.fg(kind_color, s), Show(self.msg.as_ref()))?;
126
127 let groups = self.get_source_groups(&mut cache);
128
129 let line_no_width = groups
131 .iter()
132 .filter_map(|SourceGroup { span, src_id, .. }| {
133 let src_name = cache
134 .display(src_id)
135 .map(|d| d.to_string())
136 .unwrap_or_else(|| "<unknown>".to_string());
137
138 let src = match cache.fetch(src_id) {
139 Ok(src) => src,
140 Err(e) => {
141 eprintln!("Unable to fetch source {}: {:?}", src_name, e);
142 return None;
143 }
144 };
145
146 let line_range = src.get_line_range(span);
147 Some(
148 (1..)
149 .map(|x| 10u32.pow(x))
150 .take_while(|x| line_range.end as u32 / x != 0)
151 .count()
152 + 1,
153 )
154 })
155 .max()
156 .unwrap_or(0);
157
158 let groups_len = groups.len();
160 for (
161 group_idx,
162 SourceGroup {
163 src_id,
164 span,
165 labels,
166 },
167 ) in groups.into_iter().enumerate()
168 {
169 let src_name = cache
170 .display(src_id)
171 .map(|d| d.to_string())
172 .unwrap_or_else(|| "<unknown>".to_string());
173
174 let src = match cache.fetch(src_id) {
175 Ok(src) => src,
176 Err(e) => {
177 eprintln!("Unable to fetch source {}: {:?}", src_name, e);
178 continue;
179 }
180 };
181
182 let line_range = src.get_line_range(&span);
183
184 let location = if src_id == self.location.0.borrow() {
186 self.location.1
187 } else {
188 labels[0].label.span.start()
189 };
190 let (line_no, col_no) = src
191 .get_offset_line(location)
192 .map(|(_, idx, col)| (format!("{}", idx + 1), format!("{}", col + 1)))
193 .unwrap_or_else(|| ('?'.to_string(), '?'.to_string()));
194 let line_ref = format!(":{}:{}", line_no, col_no);
195 writeln!(
196 w,
197 "{}{}{}{}{}{}{}",
198 Show((' ', line_no_width + 2)),
199 if group_idx == 0 {
200 draw.ltop
201 } else {
202 draw.lcross
203 }
204 .fg(self.config.margin_color(), s),
205 draw.hbar.fg(self.config.margin_color(), s),
206 draw.lbox.fg(self.config.margin_color(), s),
207 src_name,
208 line_ref,
209 draw.rbox.fg(self.config.margin_color(), s),
210 )?;
211
212 if !self.config.compact {
213 writeln!(
214 w,
215 "{}{}",
216 Show((' ', line_no_width + 2)),
217 draw.vbar.fg(self.config.margin_color(), s)
218 )?;
219 }
220
221 struct LineLabel<'a, S> {
222 col: usize,
223 label: &'a Label<S>,
224 multi: bool,
225 draw_msg: bool,
226 }
227
228 let mut multi_labels = Vec::new();
230 for label_info in &labels {
231 if matches!(label_info.kind, LabelKind::Multiline) {
232 multi_labels.push(&label_info.label);
233 }
234 }
235
236 multi_labels.sort_by_key(|m| -(m.span.len() as isize));
238
239 let write_margin = |w: &mut W,
240 idx: usize,
241 is_line: bool,
242 is_ellipsis: bool,
243 draw_labels: bool,
244 report_row: Option<(usize, bool)>,
245 line_labels: &[LineLabel<S>],
246 margin_label: &Option<LineLabel<S>>|
247 -> std::io::Result<()> {
248 let line_no_margin = if is_line && !is_ellipsis {
249 let line_no = format!("{}", idx + 1);
250 format!(
251 "{}{} {}",
252 Show((' ', line_no_width - line_no.chars().count())),
253 line_no,
254 draw.vbar,
255 )
256 .fg(self.config.margin_color(), s)
257 } else {
258 format!(
259 "{}{}",
260 Show((' ', line_no_width + 1)),
261 if is_ellipsis {
262 draw.vbar_gap
263 } else {
264 draw.vbar
265 }
266 )
267 .fg(self.config.skipped_margin_color(), s)
268 };
269
270 write!(
271 w,
272 " {}{}",
273 line_no_margin,
274 Show(Some(' ').filter(|_| !self.config.compact)),
275 )?;
276
277 if draw_labels {
279 for col in 0..multi_labels.len() + (multi_labels.len() > 0) as usize {
280 let mut corner = None;
281 let mut hbar = None;
282 let mut vbar: Option<&&Label<S>> = None;
283 let mut margin_ptr = None;
284
285 let multi_label = multi_labels.get(col);
286 let line_span = src.line(idx).unwrap().span();
287
288 for (i, label) in multi_labels[0..(col + 1).min(multi_labels.len())]
289 .iter()
290 .enumerate()
291 {
292 let margin = margin_label
293 .as_ref()
294 .filter(|m| **label as *const _ == m.label as *const _);
295
296 if label.span.start() <= line_span.end
297 && label.span.end() > line_span.start
298 {
299 let is_parent = i != col;
300 let is_start = line_span.contains(&label.span.start());
301 let is_end = line_span.contains(&label.last_offset());
302
303 if let Some(margin) = margin.filter(|_| is_line) {
304 margin_ptr = Some((margin, is_start));
305 } else if !is_start && (!is_end || is_line) {
306 vbar = vbar.or(Some(*label).filter(|_| !is_parent));
307 } else if let Some((report_row, is_arrow)) = report_row {
308 let label_row = line_labels
309 .iter()
310 .enumerate()
311 .find(|(_, l)| **label as *const _ == l.label as *const _)
312 .map_or(0, |(r, _)| r);
313 if report_row == label_row {
314 if let Some(margin) = margin {
315 vbar = Some(&margin.label).filter(|_| col == i);
316 if is_start {
317 continue;
318 }
319 }
320
321 if is_arrow {
322 hbar = Some(**label);
323 if !is_parent {
324 corner = Some((label, is_start));
325 }
326 } else if !is_start {
327 vbar = vbar.or(Some(*label).filter(|_| !is_parent));
328 }
329 } else {
330 vbar = vbar.or(Some(*label).filter(|_| {
331 !is_parent && (is_start ^ (report_row < label_row))
332 }));
333 }
334 }
335 }
336 }
337
338 if let (Some((margin, _is_start)), true) = (margin_ptr, is_line) {
339 let is_col = multi_label
340 .map_or(false, |ml| **ml as *const _ == margin.label as *const _);
341 let is_limit = col + 1 == multi_labels.len();
342 if !is_col && !is_limit {
343 hbar = hbar.or(Some(margin.label));
344 }
345 }
346
347 hbar = hbar.filter(|l| {
348 margin_label
349 .as_ref()
350 .map_or(true, |margin| margin.label as *const _ != *l as *const _)
351 || !is_line
352 });
353
354 let (a, b) = if let Some((label, is_start)) = corner {
355 (
356 if is_start { draw.ltop } else { draw.lbot }.fg(label.color, s),
357 draw.hbar.fg(label.color, s),
358 )
359 } else if let Some(label) =
360 hbar.filter(|_| vbar.is_some() && !self.config.cross_gap)
361 {
362 (draw.xbar.fg(label.color, s), draw.hbar.fg(label.color, s))
363 } else if let Some(label) = hbar {
364 (draw.hbar.fg(label.color, s), draw.hbar.fg(label.color, s))
365 } else if let Some(label) = vbar {
366 (
367 if is_ellipsis {
368 draw.vbar_gap
369 } else {
370 draw.vbar
371 }
372 .fg(label.color, s),
373 ' '.fg(None, s),
374 )
375 } else if let (Some((margin, is_start)), true) = (margin_ptr, is_line) {
376 let is_col = multi_label
377 .map_or(false, |ml| **ml as *const _ == margin.label as *const _);
378 let is_limit = col == multi_labels.len();
379 (
380 if is_limit {
381 draw.rarrow
382 } else if is_col {
383 if is_start {
384 draw.ltop
385 } else {
386 draw.lcross
387 }
388 } else {
389 draw.hbar
390 }
391 .fg(margin.label.color, s),
392 if !is_limit { draw.hbar } else { ' ' }.fg(margin.label.color, s),
393 )
394 } else {
395 (' '.fg(None, s), ' '.fg(None, s))
396 };
397 write!(w, "{}", a)?;
398 if !self.config.compact {
399 write!(w, "{}", b)?;
400 }
401 }
402 }
403
404 Ok(())
405 };
406
407 let mut is_ellipsis = false;
408 for idx in line_range {
409 let line = if let Some(line) = src.line(idx) {
410 line
411 } else {
412 continue;
413 };
414
415 let margin_label = multi_labels
416 .iter()
417 .enumerate()
418 .filter_map(|(_i, label)| {
419 let is_start = line.span().contains(&label.span.start());
420 let is_end = line.span().contains(&label.last_offset());
421 if is_start {
422 Some(LineLabel {
424 col: label.span.start() - line.offset(),
425 label: **label,
426 multi: true,
427 draw_msg: false, })
429 } else if is_end {
430 Some(LineLabel {
431 col: label.last_offset() - line.offset(),
432 label: **label,
433 multi: true,
434 draw_msg: true, })
436 } else {
437 None
438 }
439 })
440 .min_by_key(|ll| (ll.col, !ll.label.span.start()));
441
442 let mut line_labels = multi_labels
444 .iter()
445 .enumerate()
446 .filter_map(|(_i, label)| {
447 let is_start = line.span().contains(&label.span.start());
448 let is_end = line.span().contains(&label.last_offset());
449 if is_start
450 && margin_label
451 .as_ref()
452 .map_or(true, |m| **label as *const _ != m.label as *const _)
453 {
454 Some(LineLabel {
456 col: label.span.start() - line.offset(),
457 label: **label,
458 multi: true,
459 draw_msg: false, })
461 } else if is_end {
462 Some(LineLabel {
463 col: label.last_offset() - line.offset(),
464 label: **label,
465 multi: true,
466 draw_msg: true, })
468 } else {
469 None
470 }
471 })
472 .collect::<Vec<_>>();
473
474 for label_info in labels.iter().filter(|l| {
475 l.label.span.start() >= line.span().start
476 && l.label.span.end() <= line.span().end
477 }) {
478 if matches!(label_info.kind, LabelKind::Inline) {
479 line_labels.push(LineLabel {
480 col: match &self.config.label_attach {
481 LabelAttach::Start => label_info.label.span.start(),
482 LabelAttach::Middle => {
483 (label_info.label.span.start() + label_info.label.span.end())
484 / 2
485 }
486 LabelAttach::End => label_info.label.last_offset(),
487 }
488 .max(label_info.label.span.start())
489 - line.offset(),
490 label: label_info.label,
491 multi: false,
492 draw_msg: true,
493 });
494 }
495 }
496
497 if line_labels.len() == 0 && margin_label.is_none() {
499 let within_label = multi_labels
500 .iter()
501 .any(|label| label.span.contains(line.span().start()));
502 if !is_ellipsis && within_label {
503 is_ellipsis = true;
504 } else {
505 if !self.config.compact && !is_ellipsis {
506 write_margin(&mut w, idx, false, is_ellipsis, false, None, &[], &None)?;
507 write!(w, "\n")?;
508 }
509 is_ellipsis = true;
510 continue;
511 }
512 } else {
513 is_ellipsis = false;
514 }
515
516 line_labels.sort_by_key(|ll| (ll.label.order, ll.col, !ll.label.span.start()));
518
519 let arrow_end_space = if self.config.compact { 1 } else { 2 };
521 let arrow_len = line_labels.iter().fold(0, |l, ll| {
522 if ll.multi {
523 line.len()
524 } else {
525 l.max(ll.label.span.end().saturating_sub(line.offset()))
526 }
527 }) + arrow_end_space;
528
529 let get_vbar = |col, row| {
531 line_labels
532 .iter()
533 .enumerate()
535 .filter(|(_, ll)| {
536 ll.label.msg.is_some()
537 && margin_label
538 .as_ref()
539 .map_or(true, |m| ll.label as *const _ != m.label as *const _)
540 })
541 .find(|(j, ll)| {
542 ll.col == col && ((row <= *j && !ll.multi) || (row <= *j && ll.multi))
543 })
544 .map(|(_, ll)| ll)
545 };
546
547 let get_highlight = |col| {
548 margin_label
549 .iter()
550 .map(|ll| ll.label)
551 .chain(multi_labels.iter().map(|l| **l))
552 .chain(line_labels.iter().map(|l| l.label))
553 .filter(|l| l.span.contains(line.offset() + col))
554 .min_by_key(|l| (-l.priority, l.span.len()))
556 };
557
558 let get_underline = |col| {
559 line_labels
560 .iter()
561 .filter(|ll| {
562 self.config.underlines
563 && !ll.multi
565 && ll.label.span.contains(line.offset() + col)
566 })
567 .min_by_key(|ll| (-ll.label.priority, ll.label.span.len()))
569 };
570
571 write_margin(
573 &mut w,
574 idx,
575 true,
576 is_ellipsis,
577 true,
578 None,
579 &line_labels,
580 &margin_label,
581 )?;
582
583 if !is_ellipsis {
585 for (col, c) in line.chars().enumerate() {
586 let color = if let Some(highlight) = get_highlight(col) {
587 highlight.color
588 } else {
589 self.config.unimportant_color()
590 };
591 let (c, width) = self.config.char_width(c, col);
592 if c.is_whitespace() {
593 for _ in 0..width {
594 write!(w, "{}", c.fg(color, s))?;
595 }
596 } else {
597 write!(w, "{}", c.fg(color, s))?;
598 };
599 }
600 }
601 write!(w, "\n")?;
602
603 for row in 0..line_labels.len() {
605 let line_label = &line_labels[row];
606
607 if !self.config.compact {
608 write_margin(
610 &mut w,
611 idx,
612 false,
613 is_ellipsis,
614 true,
615 Some((row, false)),
616 &line_labels,
617 &margin_label,
618 )?;
619 let mut chars = line.chars();
621 for col in 0..arrow_len {
622 let width =
623 chars.next().map_or(1, |c| self.config.char_width(c, col).1);
624
625 let vbar = get_vbar(col, row);
626 let underline = get_underline(col).filter(|_| row == 0);
627 let [c, tail] = if let Some(vbar_ll) = vbar {
628 let [c, tail] = if underline.is_some() {
629 if vbar_ll.label.span.len() <= 1 || true {
631 [draw.underbar, draw.underline]
632 } else if line.offset() + col == vbar_ll.label.span.start() {
633 [draw.ltop, draw.underbar]
634 } else if line.offset() + col == vbar_ll.label.last_offset() {
635 [draw.rtop, draw.underbar]
636 } else {
637 [draw.underbar, draw.underline]
638 }
639 } else if vbar_ll.multi && row == 0 && self.config.multiline_arrows
640 {
641 [draw.uarrow, ' ']
642 } else {
643 [draw.vbar, ' ']
644 };
645 [
646 c.fg(vbar_ll.label.color, s),
647 tail.fg(vbar_ll.label.color, s),
648 ]
649 } else if let Some(underline_ll) = underline {
650 [draw.underline.fg(underline_ll.label.color, s); 2]
651 } else {
652 [' '.fg(None, s); 2]
653 };
654
655 for i in 0..width {
656 write!(w, "{}", if i == 0 { c } else { tail })?;
657 }
658 }
659 write!(w, "\n")?;
660 }
661
662 write_margin(
664 &mut w,
665 idx,
666 false,
667 is_ellipsis,
668 true,
669 Some((row, true)),
670 &line_labels,
671 &margin_label,
672 )?;
673 let mut chars = line.chars();
675 for col in 0..arrow_len {
676 let width = chars.next().map_or(1, |c| self.config.char_width(c, col).1);
677
678 let is_hbar = (((col > line_label.col) ^ line_label.multi)
679 || (line_label.label.msg.is_some()
680 && line_label.draw_msg
681 && col > line_label.col))
682 && line_label.label.msg.is_some();
683 let [c, tail] = if col == line_label.col
684 && line_label.label.msg.is_some()
685 && margin_label.as_ref().map_or(true, |m| {
686 line_label.label as *const _ != m.label as *const _
687 }) {
688 [
689 if line_label.multi {
690 if line_label.draw_msg {
691 draw.mbot
692 } else {
693 draw.rbot
694 }
695 } else {
696 draw.lbot
697 }
698 .fg(line_label.label.color, s),
699 draw.hbar.fg(line_label.label.color, s),
700 ]
701 } else if let Some(vbar_ll) = get_vbar(col, row)
702 .filter(|_| (col != line_label.col || line_label.label.msg.is_some()))
703 {
704 if !self.config.cross_gap && is_hbar {
705 [
706 draw.xbar.fg(line_label.label.color, s),
707 ' '.fg(line_label.label.color, s),
708 ]
709 } else if is_hbar {
710 [draw.hbar.fg(line_label.label.color, s); 2]
711 } else {
712 [
713 if vbar_ll.multi && row == 0 && self.config.compact {
714 draw.uarrow
715 } else {
716 draw.vbar
717 }
718 .fg(vbar_ll.label.color, s),
719 ' '.fg(line_label.label.color, s),
720 ]
721 }
722 } else if is_hbar {
723 [draw.hbar.fg(line_label.label.color, s); 2]
724 } else {
725 [' '.fg(None, s); 2]
726 };
727
728 if width > 0 {
729 write!(w, "{}", c)?;
730 }
731 for _ in 1..width {
732 write!(w, "{}", tail)?;
733 }
734 }
735 if line_label.draw_msg {
736 write!(w, " {}", Show(line_label.label.msg.as_ref()))?;
737 }
738 write!(w, "\n")?;
739 }
740 }
741
742 let is_final_group = group_idx + 1 == groups_len;
743
744 if let (Some(note), true) = (&self.help, is_final_group) {
746 if !self.config.compact {
747 write_margin(&mut w, 0, false, false, true, Some((0, false)), &[], &None)?;
748 write!(w, "\n")?;
749 }
750 write_margin(&mut w, 0, false, false, true, Some((0, false)), &[], &None)?;
751 write!(w, "{}: {}\n", "Help".fg(self.config.note_color(), s), note)?;
752 }
753
754 if let (Some(note), true) = (&self.note, is_final_group) {
756 if !self.config.compact {
757 write_margin(&mut w, 0, false, false, true, Some((0, false)), &[], &None)?;
758 write!(w, "\n")?;
759 }
760 write_margin(&mut w, 0, false, false, true, Some((0, false)), &[], &None)?;
761 write!(w, "{}: {}\n", "Note".fg(self.config.note_color(), s), note)?;
762 }
763
764 if !self.config.compact {
766 if is_final_group {
767 let final_margin =
768 format!("{}{}", Show((draw.hbar, line_no_width + 2)), draw.rbot);
769 writeln!(w, "{}", final_margin.fg(self.config.margin_color(), s))?;
770 } else {
771 writeln!(
772 w,
773 "{}{}",
774 Show((' ', line_no_width + 2)),
775 draw.vbar.fg(self.config.margin_color(), s)
776 )?;
777 }
778 }
779 }
780 Ok(())
781 }
782}
783
784impl<S: Span> Label<S> {
785 fn last_offset(&self) -> usize {
786 self.span.end().saturating_sub(1).max(self.span.start())
787 }
788}