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