1use crate::{FileCache, FileID};
2use std::ops::Range;
3
4use super::{
5 draw::{self, StreamAwareFmt, StreamType},
6 CharSet, Label, LabelAttach, Report, Show, Span, Write,
7};
8
9enum LabelKind {
18 Inline,
19 Multiline,
20}
21
22struct LabelInfo<'a> {
23 kind: LabelKind,
24 label: &'a Label,
25}
26
27struct SourceGroup<'a> {
28 src_id: &'a FileID,
29 span: Range<usize>,
30 labels: Vec<LabelInfo<'a>>,
31}
32
33impl Report {
34 fn get_source_groups(&self, cache: &mut FileCache) -> Vec<SourceGroup> {
35 let mut groups = Vec::new();
36 for label in self.labels.iter() {
37 let src = match cache.fetch(label.span.source()) {
38 Ok(src) => src,
39 Err(e) => {
40 let src_display = cache.display(label.span.source());
41 eprintln!("Unable to fetch source '{}': {:?}", Show(src_display), e);
42 continue;
43 }
44 };
45
46 assert!(label.span.start() <= label.span.end(), "Label start is after its end");
47
48 let start_line = src.get_offset_line(label.span.start()).map(|(_, l, _)| l);
49 let end_line = src.get_offset_line(label.span.end().saturating_sub(1).max(label.span.start())).map(|(_, l, _)| l);
50
51 let label_info =
52 LabelInfo { kind: if start_line == end_line { LabelKind::Inline } else { LabelKind::Multiline }, label };
53
54 if let Some(group) = groups.iter_mut().find(|g: &&mut SourceGroup| g.src_id == label.span.source()) {
55 group.span.start = group.span.start.min(label.span.start());
56 group.span.end = group.span.end.max(label.span.end());
57 group.labels.push(label_info);
58 }
59 else {
60 groups.push(SourceGroup {
61 src_id: label.span.source(),
62 span: label.span.start()..label.span.end(),
63 labels: vec![label_info],
64 });
65 }
66 }
67 groups
68 }
69
70 pub fn write<W: Write>(&self, cache: FileCache, w: W) -> std::io::Result<()> {
77 self.write_for_stream(cache, w, StreamType::Stderr)
78 }
79
80 pub fn write_for_stdout<W: Write>(&self, cache: FileCache, w: W) -> std::io::Result<()> {
83 self.write_for_stream(cache, w, StreamType::Stdout)
84 }
85
86 fn write_for_stream<W: Write>(&self, mut cache: FileCache, mut w: W, s: StreamType) -> std::io::Result<()> {
89 let draw = match self.config.char_set {
90 CharSet::Unicode => draw::Characters::unicode(),
91 CharSet::Ascii => draw::Characters::ascii(),
92 };
93
94 let kind_color = self.kind.get_color();
96 let head = match &self.code {
97 Some(s) => format!("{:?}[{:04}]:", self.kind, s),
98 None => format!("{:?}:", self.kind),
99 };
100 write!(w, "{}", head.fg(kind_color, s))?;
101 if self.message.is_empty() {
102 writeln!(w)?;
103 }
104 else {
105 writeln!(w, " {}", self.message)?;
106 }
107 let groups = self.get_source_groups(&mut cache);
108
109 let line_no_width = groups
111 .iter()
112 .filter_map(|SourceGroup { span, src_id, .. }| {
113 let src_name = cache.display(src_id).map(|d| d.to_string()).unwrap_or_else(|| "<unknown>".to_string());
114
115 let src = match cache.fetch(src_id) {
116 Ok(src) => src,
117 Err(e) => {
118 eprintln!("Unable to fetch source {}: {:?}", src_name, e);
119 return None;
120 }
121 };
122
123 let line_range = src.get_line_range(span);
124 Some((1..).map(|x| 10u32.pow(x)).take_while(|x| line_range.end as u32 / x != 0).count() + 1)
125 })
126 .max()
127 .unwrap_or(0);
128
129 let groups_len = groups.len();
131 for (group_idx, SourceGroup { src_id, span, labels }) in groups.into_iter().enumerate() {
132 let src_name = cache.display(src_id).map(|d| d.to_string()).unwrap_or_else(|| "<unknown>".to_string());
133
134 let src = match cache.fetch(src_id) {
135 Ok(src) => src,
136 Err(e) => {
137 eprintln!("Unable to fetch source {}: {:?}", src_name, e);
138 continue;
139 }
140 };
141
142 let line_range = src.get_line_range(&span);
143
144 let location = if src_id == &self.location.0 { self.location.1 } else { labels[0].label.span.start() };
146 let (line_no, col_no) = src
147 .get_offset_line(location)
148 .map(|(_, idx, col)| (format!("{}", idx + 1), format!("{}", col + 1)))
149 .unwrap_or_else(|| ('?'.to_string(), '?'.to_string()));
150 let line_ref = format!(":{}:{}", line_no, col_no);
151 writeln!(
152 w,
153 "{}{}{}{}{}{}{}",
154 Show((' ', line_no_width + 2)),
155 if group_idx == 0 { draw.ltop } else { draw.lcross }.fg(self.config.margin_color(), s),
156 draw.hbar.fg(self.config.margin_color(), s),
157 draw.lbox.fg(self.config.margin_color(), s),
158 src_name,
159 line_ref,
160 draw.rbox.fg(self.config.margin_color(), s),
161 )?;
162
163 if !self.config.compact {
164 writeln!(w, "{}{}", Show((' ', line_no_width + 2)), draw.vbar.fg(self.config.margin_color(), s))?;
165 }
166
167 struct LineLabel<'a> {
168 col: usize,
169 label: &'a Label,
170 multi: bool,
171 draw_msg: bool,
172 }
173
174 let mut multi_labels = Vec::new();
176 for label_info in &labels {
177 if matches!(label_info.kind, LabelKind::Multiline) {
178 multi_labels.push(&label_info.label);
179 }
180 }
181
182 multi_labels.sort_by_key(|m| -(m.span.len() as isize));
184
185 let write_margin = |w: &mut W,
186 idx: usize,
187 is_line: bool,
188 is_ellipsis: bool,
189 draw_labels: bool,
190 report_row: Option<(usize, bool)>,
191 line_labels: &[LineLabel],
192 margin_label: &Option<LineLabel>|
193 -> std::io::Result<()> {
194 let line_no_margin = if is_line && !is_ellipsis {
195 let line_no = format!("{}", idx + 1);
196 format!("{}{} {}", Show((' ', line_no_width - line_no.chars().count())), line_no, draw.vbar,)
197 .fg(self.config.margin_color(), s)
198 }
199 else {
200 format!("{}{}", Show((' ', line_no_width + 1)), if is_ellipsis { draw.vbar_gap } else { draw.vbar })
201 .fg(self.config.skipped_margin_color(), s)
202 };
203
204 write!(w, " {}{}", line_no_margin, Show(Some(' ').filter(|_| !self.config.compact)),)?;
205
206 if draw_labels {
208 for col in 0..multi_labels.len() + (multi_labels.len() > 0) as usize {
209 let mut corner = None;
210 let mut hbar = None;
211 let mut vbar: Option<&&Label> = None;
212 let mut margin_ptr = None;
213
214 let multi_label = multi_labels.get(col);
215 let line_span = src.line(idx).unwrap().span();
216
217 for (i, label) in multi_labels[0..(col + 1).min(multi_labels.len())].iter().enumerate() {
218 let margin = margin_label.as_ref().filter(|m| **label as *const _ == m.label as *const _);
219
220 if label.span.start() <= line_span.end && label.span.end() > line_span.start {
221 let is_parent = i != col;
222 let is_start = line_span.contains(&label.span.start());
223 let is_end = line_span.contains(&label.last_offset());
224
225 if let Some(margin) = margin.filter(|_| is_line) {
226 margin_ptr = Some((margin, is_start));
227 }
228 else if !is_start && (!is_end || is_line) {
229 vbar = vbar.or(Some(*label).filter(|_| !is_parent));
230 }
231 else if let Some((report_row, is_arrow)) = report_row {
232 let label_row = line_labels
233 .iter()
234 .enumerate()
235 .find(|(_, l)| **label as *const _ == l.label as *const _)
236 .map_or(0, |(r, _)| r);
237 if report_row == label_row {
238 if let Some(margin) = margin {
239 vbar = Some(&margin.label).filter(|_| col == i);
240 if is_start {
241 continue;
242 }
243 }
244
245 if is_arrow {
246 hbar = Some(**label);
247 if !is_parent {
248 corner = Some((label, is_start));
249 }
250 }
251 else if !is_start {
252 vbar = vbar.or(Some(*label).filter(|_| !is_parent));
253 }
254 }
255 else {
256 vbar = vbar
257 .or(Some(*label).filter(|_| !is_parent && (is_start ^ (report_row < label_row))));
258 }
259 }
260 }
261 }
262
263 if let (Some((margin, _is_start)), true) = (margin_ptr, is_line) {
264 let is_col = multi_label.map_or(false, |ml| **ml as *const _ == margin.label as *const _);
265 let is_limit = col + 1 == multi_labels.len();
266 if !is_col && !is_limit {
267 hbar = hbar.or(Some(margin.label));
268 }
269 }
270
271 hbar = hbar.filter(|l| {
272 margin_label.as_ref().map_or(true, |margin| margin.label as *const _ != *l as *const _) || !is_line
273 });
274
275 let (a, b) = if let Some((label, is_start)) = corner {
276 (if is_start { draw.ltop } else { draw.lbot }.fg(label.color, s), draw.hbar.fg(label.color, s))
277 }
278 else if let Some(label) = hbar.filter(|_| vbar.is_some() && !self.config.cross_gap) {
279 (draw.xbar.fg(label.color, s), draw.hbar.fg(label.color, s))
280 }
281 else if let Some(label) = hbar {
282 (draw.hbar.fg(label.color, s), draw.hbar.fg(label.color, s))
283 }
284 else if let Some(label) = vbar {
285 (if is_ellipsis { draw.vbar_gap } else { draw.vbar }.fg(label.color, s), ' '.fg(None, s))
286 }
287 else if let (Some((margin, is_start)), true) = (margin_ptr, is_line) {
288 let is_col = multi_label.map_or(false, |ml| **ml as *const _ == margin.label as *const _);
289 let is_limit = col == multi_labels.len();
290 (
291 if is_limit {
292 draw.rarrow
293 }
294 else if is_col {
295 if is_start { draw.ltop } else { draw.lcross }
296 }
297 else {
298 draw.hbar
299 }
300 .fg(margin.label.color, s),
301 if !is_limit { draw.hbar } else { ' ' }.fg(margin.label.color, s),
302 )
303 }
304 else {
305 (' '.fg(None, s), ' '.fg(None, s))
306 };
307 write!(w, "{}", a)?;
308 if !self.config.compact {
309 write!(w, "{}", b)?;
310 }
311 }
312 }
313
314 Ok(())
315 };
316
317 let mut is_ellipsis = false;
318 for idx in line_range {
319 let line = if let Some(line) = src.line(idx) {
320 line
321 }
322 else {
323 continue;
324 };
325
326 let margin_label = multi_labels
327 .iter()
328 .enumerate()
329 .filter_map(|(_i, label)| {
330 let is_start = line.span().contains(&label.span.start());
331 let is_end = line.span().contains(&label.last_offset());
332 if is_start {
333 Some(LineLabel {
335 col: label.span.start() - line.offset(),
336 label: **label,
337 multi: true,
338 draw_msg: false, })
340 }
341 else if is_end {
342 Some(LineLabel {
343 col: label.last_offset() - line.offset(),
344 label: **label,
345 multi: true,
346 draw_msg: true, })
348 }
349 else {
350 None
351 }
352 })
353 .min_by_key(|ll| (ll.col, !ll.label.span.start()));
354
355 let mut line_labels = multi_labels
357 .iter()
358 .enumerate()
359 .filter_map(|(_i, label)| {
360 let is_start = line.span().contains(&label.span.start());
361 let is_end = line.span().contains(&label.last_offset());
362 if is_start && margin_label.as_ref().map_or(true, |m| **label as *const _ != m.label as *const _) {
363 Some(LineLabel {
365 col: label.span.start() - line.offset(),
366 label: **label,
367 multi: true,
368 draw_msg: false, })
370 }
371 else if is_end {
372 Some(LineLabel {
373 col: label.last_offset() - line.offset(),
374 label: **label,
375 multi: true,
376 draw_msg: true, })
378 }
379 else {
380 None
381 }
382 })
383 .collect::<Vec<_>>();
384
385 for label_info in
386 labels.iter().filter(|l| l.label.span.start() >= line.span().start && l.label.span.end() <= line.span().end)
387 {
388 if matches!(label_info.kind, LabelKind::Inline) {
389 line_labels.push(LineLabel {
390 col: match &self.config.label_attach {
391 LabelAttach::Start => label_info.label.span.start(),
392 LabelAttach::Middle => (label_info.label.span.start() + label_info.label.span.end()) / 2,
393 LabelAttach::End => label_info.label.last_offset(),
394 }
395 .max(label_info.label.span.start())
396 - line.offset(),
397 label: label_info.label,
398 multi: false,
399 draw_msg: true,
400 });
401 }
402 }
403
404 if line_labels.len() == 0 && margin_label.is_none() {
406 let within_label = multi_labels.iter().any(|label| label.span.contains(line.span().start));
407 if !is_ellipsis && within_label {
408 is_ellipsis = true;
409 }
410 else {
411 if !self.config.compact && !is_ellipsis {
412 write_margin(&mut w, idx, false, is_ellipsis, false, None, &[], &None)?;
413 write!(w, "\n")?;
414 }
415 is_ellipsis = true;
416 continue;
417 }
418 }
419 else {
420 is_ellipsis = false;
421 }
422
423 line_labels.sort_by_key(|ll| (ll.label.order, ll.col, !ll.label.span.start()));
425
426 let arrow_end_space = if self.config.compact { 1 } else { 2 };
428 let arrow_len = line_labels.iter().fold(0, |l, ll| {
429 if ll.multi { line.len() } else { l.max(ll.label.span.end().saturating_sub(line.offset())) }
430 }) + arrow_end_space;
431
432 let get_vbar = |col, row| {
434 line_labels
435 .iter()
436 .enumerate()
438 .filter(|(_, ll)| {
439 ll.label.msg.is_some()
440 && margin_label.as_ref().map_or(true, |m| ll.label as *const _ != m.label as *const _)
441 })
442 .find(|(j, ll)| ll.col == col && ((row <= *j && !ll.multi) || (row <= *j && ll.multi)))
443 .map(|(_, ll)| ll)
444 };
445
446 let get_highlight = |col| {
447 margin_label
448 .iter()
449 .map(|ll| ll.label)
450 .chain(multi_labels.iter().map(|l| **l))
451 .chain(line_labels.iter().map(|l| l.label))
452 .filter(|l| l.span.contains(line.offset() + col))
453 .min_by_key(|l| (-l.priority, l.span.len()))
455 };
456
457 let get_underline = |col| {
458 line_labels
459 .iter()
460 .filter(|ll| {
461 self.config.underlines
462 && !ll.multi
464 && ll.label.span.contains(line.offset() + col)
465 })
466 .min_by_key(|ll| (-ll.label.priority, ll.label.span.len()))
468 };
469
470 write_margin(&mut w, idx, true, is_ellipsis, true, None, &line_labels, &margin_label)?;
472
473 if !is_ellipsis {
475 for (col, c) in line.chars().enumerate() {
476 let color = if let Some(highlight) = get_highlight(col) {
477 highlight.color
478 }
479 else {
480 self.config.unimportant_color()
481 };
482 let (c, width) = self.config.char_width(c, col);
483 if c.is_whitespace() {
484 for _ in 0..width {
485 write!(w, "{}", c.fg(color, s))?;
486 }
487 }
488 else {
489 write!(w, "{}", c.fg(color, s))?;
490 };
491 }
492 }
493 write!(w, "\n")?;
494
495 for row in 0..line_labels.len() {
497 let line_label = &line_labels[row];
498
499 if !self.config.compact {
500 write_margin(&mut w, idx, false, is_ellipsis, true, Some((row, false)), &line_labels, &margin_label)?;
502 let mut chars = line.chars();
504 for col in 0..arrow_len {
505 let width = chars.next().map_or(1, |c| self.config.char_width(c, col).1);
506
507 let vbar = get_vbar(col, row);
508 let underline = get_underline(col).filter(|_| row == 0);
509 let [c, tail] = if let Some(vbar_ll) = vbar {
510 let [c, tail] = if underline.is_some() {
511 if vbar_ll.label.span.len() <= 1 || true {
513 [draw.underbar, draw.underline]
514 }
515 else if line.offset() + col == vbar_ll.label.span.start() {
516 [draw.ltop, draw.underbar]
517 }
518 else if line.offset() + col == vbar_ll.label.last_offset() {
519 [draw.rtop, draw.underbar]
520 }
521 else {
522 [draw.underbar, draw.underline]
523 }
524 }
525 else if vbar_ll.multi && row == 0 && self.config.multiline_arrows {
526 [draw.uarrow, ' ']
527 }
528 else {
529 [draw.vbar, ' ']
530 };
531 [c.fg(vbar_ll.label.color, s), tail.fg(vbar_ll.label.color, s)]
532 }
533 else if let Some(underline_ll) = underline {
534 [draw.underline.fg(underline_ll.label.color, s); 2]
535 }
536 else {
537 [' '.fg(None, s); 2]
538 };
539
540 for i in 0..width {
541 write!(w, "{}", if i == 0 { c } else { tail })?;
542 }
543 }
544 write!(w, "\n")?;
545 }
546
547 write_margin(&mut w, idx, false, is_ellipsis, true, Some((row, true)), &line_labels, &margin_label)?;
549 let mut chars = line.chars();
551 for col in 0..arrow_len {
552 let width = chars.next().map_or(1, |c| self.config.char_width(c, col).1);
553
554 let is_hbar = (((col > line_label.col) ^ line_label.multi)
555 || (line_label.label.msg.is_some() && line_label.draw_msg && col > line_label.col))
556 && line_label.label.msg.is_some();
557 let [c, tail] = if col == line_label.col
558 && line_label.label.msg.is_some()
559 && margin_label.as_ref().map_or(true, |m| line_label.label as *const _ != m.label as *const _)
560 {
561 [
562 if line_label.multi {
563 if line_label.draw_msg { draw.mbot } else { draw.rbot }
564 }
565 else {
566 draw.lbot
567 }
568 .fg(line_label.label.color, s),
569 draw.hbar.fg(line_label.label.color, s),
570 ]
571 }
572 else if let Some(vbar_ll) =
573 get_vbar(col, row).filter(|_| (col != line_label.col || line_label.label.msg.is_some()))
574 {
575 if !self.config.cross_gap && is_hbar {
576 [draw.xbar.fg(line_label.label.color, s), ' '.fg(line_label.label.color, s)]
577 }
578 else if is_hbar {
579 [draw.hbar.fg(line_label.label.color, s); 2]
580 }
581 else {
582 [
583 if vbar_ll.multi && row == 0 && self.config.compact { draw.uarrow } else { draw.vbar }
584 .fg(vbar_ll.label.color, s),
585 ' '.fg(line_label.label.color, s),
586 ]
587 }
588 }
589 else if is_hbar {
590 [draw.hbar.fg(line_label.label.color, s); 2]
591 }
592 else {
593 [' '.fg(None, s); 2]
594 };
595
596 if width > 0 {
597 write!(w, "{}", c)?;
598 }
599 for _ in 1..width {
600 write!(w, "{}", tail)?;
601 }
602 }
603 if line_label.draw_msg {
604 write!(w, " {}", Show(line_label.label.msg.as_ref()))?;
605 }
606 write!(w, "\n")?;
607 }
608 }
609
610 let is_final_group = group_idx + 1 == groups_len;
611
612 if let (Some(note), true) = (&self.help, is_final_group) {
614 if !self.config.compact {
615 write_margin(&mut w, 0, false, false, true, Some((0, false)), &[], &None)?;
616 write!(w, "\n")?;
617 }
618 write_margin(&mut w, 0, false, false, true, Some((0, false)), &[], &None)?;
619 write!(w, "{}: {}\n", "Help".fg(self.config.note_color(), s), note)?;
620 }
621
622 if let (Some(note), true) = (&self.note, is_final_group) {
624 if !self.config.compact {
625 write_margin(&mut w, 0, false, false, true, Some((0, false)), &[], &None)?;
626 write!(w, "\n")?;
627 }
628 write_margin(&mut w, 0, false, false, true, Some((0, false)), &[], &None)?;
629 write!(w, "{}: {}\n", "Note".fg(self.config.note_color(), s), note)?;
630 }
631
632 if !self.config.compact {
634 if is_final_group {
635 let final_margin = format!("{}{}", Show((draw.hbar, line_no_width + 2)), draw.rbot);
636 writeln!(w, "{}", final_margin.fg(self.config.margin_color(), s))?;
637 }
638 else {
639 writeln!(w, "{}{}", Show((' ', line_no_width + 2)), draw.vbar.fg(self.config.margin_color(), s))?;
640 }
641 }
642 }
643 Ok(())
644 }
645}
646
647impl Label {
648 fn last_offset(&self) -> usize {
649 self.span.end().saturating_sub(1).max(self.span.start())
650 }
651}