1use std::io::{self, Write};
2use std::ops::Range;
3use termcolor::{ColorSpec, WriteColor};
4
5use super::super::diagnostic::{LabelStyle, Note, Severity};
6use super::super::files::{Error, Location};
7use super::super::term::{Chars, Config, Styles};
8
9pub struct Locus {
11 pub name: String,
13 pub location: Location,
15}
16
17pub type SingleLabel<'diagnostic> = (LabelStyle, Range<usize>, &'diagnostic str);
23
24pub enum MultiLabel<'diagnostic> {
28 Top(usize),
42 Left,
48 Bottom(usize, &'diagnostic str),
55}
56
57#[derive(Copy, Clone)]
58enum VerticalBound {
59 Top,
60 Bottom,
61}
62
63type Underline = (LabelStyle, VerticalBound);
64
65pub struct Renderer<'writer, 'config> {
112 writer: &'writer mut dyn WriteColor,
113 config: &'config Config,
114}
115
116impl<'writer, 'config> Renderer<'writer, 'config> {
117 pub fn new(
119 writer: &'writer mut dyn WriteColor,
120 config: &'config Config,
121 ) -> Renderer<'writer, 'config> {
122 Renderer { writer, config }
123 }
124
125 fn chars(&self) -> &'config Chars {
126 &self.config.chars
127 }
128
129 fn styles(&self) -> &'config Styles {
130 &self.config.styles
131 }
132
133 pub fn render_header(
139 &mut self,
140 locus: Option<&Locus>,
141 severity: Severity,
142 code: Option<&str>,
143 message: &str,
144 ) -> Result<(), Error> {
145 if let Some(locus) = locus {
151 self.snippet_locus(locus)?;
152 write!(self, ": ")?;
153 }
154
155 self.set_color(self.styles().header(severity))?;
161 match severity {
162 Severity::Bug => write!(self, "bug")?,
163 Severity::Error => write!(self, "error")?,
164 Severity::Warning => write!(self, "warning")?,
165 Severity::Help => write!(self, "help")?,
166 Severity::Note => write!(self, "note")?,
167 }
168
169 if let Some(code) = &code.filter(|code| !code.is_empty()) {
175 write!(self, "[{}]", code)?;
176 }
177
178 self.set_color(&self.styles().header_message)?;
184 write!(self, ": {}", message)?;
185 self.reset()?;
186
187 writeln!(self)?;
188
189 Ok(())
190 }
191
192 pub fn render_empty(&mut self) -> Result<(), Error> {
194 writeln!(self)?;
195 Ok(())
196 }
197
198 pub fn render_snippet_start(
204 &mut self,
205 outer_padding: usize,
206 locus: &Locus,
207 ) -> Result<(), Error> {
208 self.outer_gutter(outer_padding)?;
209
210 self.set_color(&self.styles().source_border)?;
211 write!(self, "{}", self.chars().source_border_top_left)?;
212 write!(self, "{0}", self.chars().source_border_top)?;
213 self.reset()?;
214
215 write!(self, " ")?;
216 self.snippet_locus(&locus)?;
217
218 writeln!(self)?;
219
220 Ok(())
221 }
222
223 pub fn render_snippet_source(
230 &mut self,
231 outer_padding: usize,
232 line_number: usize,
233 source: &str,
234 severity: Severity,
235 single_labels: &[SingleLabel<'_>],
236 num_multi_labels: usize,
237 multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
238 ) -> Result<(), Error> {
239 let source = source.trim_end_matches(['\n', '\r', '\0'].as_ref());
242
243 {
249 self.outer_gutter_number(line_number, outer_padding)?;
251 self.border_left()?;
252
253 let mut multi_labels_iter = multi_labels.iter().peekable();
255 for label_column in 0..num_multi_labels {
256 match multi_labels_iter.peek() {
257 Some((label_index, label_style, label)) if *label_index == label_column => {
258 match label {
259 MultiLabel::Top(start)
260 if *start <= source.len() - source.trim_start().len() =>
261 {
262 self.label_multi_top_left(severity, *label_style)?;
263 }
264 MultiLabel::Top(..) => self.inner_gutter_space()?,
265 MultiLabel::Left | MultiLabel::Bottom(..) => {
266 self.label_multi_left(severity, *label_style, None)?;
267 }
268 }
269 multi_labels_iter.next();
270 }
271 Some((_, _, _)) | None => self.inner_gutter_space()?,
272 }
273 }
274
275 write!(self, " ")?;
277 let mut in_primary = false;
278 for (metrics, ch) in self.char_metrics(source.char_indices()) {
279 let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8());
280
281 let is_primary = single_labels.iter().any(|(ls, range, _)| {
283 *ls == LabelStyle::Primary && is_overlapping(range, &column_range)
284 }) || multi_labels.iter().any(|(_, ls, label)| {
285 *ls == LabelStyle::Primary
286 && match label {
287 MultiLabel::Top(start) => column_range.start >= *start,
288 MultiLabel::Left => true,
289 MultiLabel::Bottom(start, _) => column_range.end <= *start,
290 }
291 });
292
293 if is_primary && !in_primary {
295 self.set_color(self.styles().label(severity, LabelStyle::Primary))?;
296 in_primary = true;
297 } else if !is_primary && in_primary {
298 self.reset()?;
299 in_primary = false;
300 }
301
302 match ch {
303 '\t' => (0..metrics.unicode_width).try_for_each(|_| write!(self, " "))?,
304 _ => write!(self, "{}", ch)?,
305 }
306 }
307 if in_primary {
308 self.reset()?;
309 }
310 writeln!(self)?;
311 }
312
313 if !single_labels.is_empty() {
323 let mut num_messages = 0;
344 let mut max_label_start = 0;
352 let mut max_label_end = 0;
360 let mut trailing_label = None;
366
367 for (label_index, label) in single_labels.iter().enumerate() {
368 let (_, range, message) = label;
369 if !message.is_empty() {
370 num_messages += 1;
371 }
372 max_label_start = std::cmp::max(max_label_start, range.start);
373 max_label_end = std::cmp::max(max_label_end, range.end);
374 if range.end == max_label_end {
376 if message.is_empty() {
377 trailing_label = None;
378 } else {
379 trailing_label = Some((label_index, label));
380 }
381 }
382 }
383 if let Some((trailing_label_index, (_, trailing_range, _))) = trailing_label {
384 if single_labels
387 .iter()
388 .enumerate()
389 .filter(|(label_index, _)| *label_index != trailing_label_index)
390 .any(|(_, (_, range, _))| is_overlapping(trailing_range, range))
391 {
392 trailing_label = None;
395 }
396 }
397
398 self.outer_gutter(outer_padding)?;
404 self.border_left()?;
405 self.inner_gutter(severity, num_multi_labels, multi_labels)?;
406 write!(self, " ")?;
407
408 let mut previous_label_style = None;
409 let placeholder_metrics = Metrics {
410 byte_index: source.len(),
411 unicode_width: 1,
412 };
413 for (metrics, ch) in self
414 .char_metrics(source.char_indices())
415 .chain(std::iter::once((placeholder_metrics, '\0')))
423 {
424 let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8());
426 let current_label_style = single_labels
427 .iter()
428 .filter(|(_, range, _)| is_overlapping(range, &column_range))
429 .map(|(label_style, _, _)| *label_style)
430 .max_by_key(label_priority_key);
431
432 if previous_label_style != current_label_style {
434 match current_label_style {
435 None => self.reset()?,
436 Some(label_style) => {
437 self.set_color(self.styles().label(severity, label_style))?;
438 }
439 }
440 }
441
442 let caret_ch = match current_label_style {
443 Some(LabelStyle::Primary) => Some(self.chars().single_primary_caret),
444 Some(LabelStyle::Secondary) => Some(self.chars().single_secondary_caret),
445 None if metrics.byte_index < max_label_end => Some(' '),
447 None => None,
448 };
449 if let Some(caret_ch) = caret_ch {
450 (0..metrics.unicode_width).try_for_each(|_| write!(self, "{}", caret_ch))?;
452 }
453
454 previous_label_style = current_label_style;
455 }
456 if previous_label_style.is_some() {
458 self.reset()?;
459 }
460 if let Some((_, (label_style, _, message))) = trailing_label {
462 write!(self, " ")?;
463 self.set_color(self.styles().label(severity, *label_style))?;
464 write!(self, "{}", message)?;
465 self.reset()?;
466 }
467 writeln!(self)?;
468
469 if num_messages > trailing_label.iter().count() {
478 self.outer_gutter(outer_padding)?;
484 self.border_left()?;
485 self.inner_gutter(severity, num_multi_labels, multi_labels)?;
486 write!(self, " ")?;
487 self.caret_pointers(
488 severity,
489 max_label_start,
490 single_labels,
491 trailing_label,
492 source.char_indices(),
493 )?;
494 writeln!(self)?;
495
496 for (label_style, range, message) in
504 hanging_labels(single_labels, trailing_label).rev()
505 {
506 self.outer_gutter(outer_padding)?;
507 self.border_left()?;
508 self.inner_gutter(severity, num_multi_labels, multi_labels)?;
509 write!(self, " ")?;
510 self.caret_pointers(
511 severity,
512 max_label_start,
513 single_labels,
514 trailing_label,
515 source
516 .char_indices()
517 .take_while(|(byte_index, _)| *byte_index < range.start),
518 )?;
519 self.set_color(self.styles().label(severity, *label_style))?;
520 write!(self, "{}", message)?;
521 self.reset()?;
522 writeln!(self)?;
523 }
524 }
525 }
526
527 for (multi_label_index, (_, label_style, label)) in multi_labels.iter().enumerate() {
534 let (label_style, range, bottom_message) = match label {
535 MultiLabel::Left => continue, MultiLabel::Top(start) if *start <= source.len() - source.trim_start().len() => {
538 continue
539 }
540 MultiLabel::Top(range) => (*label_style, range, None),
541 MultiLabel::Bottom(range, message) => (*label_style, range, Some(message)),
542 };
543
544 self.outer_gutter(outer_padding)?;
545 self.border_left()?;
546
547 let mut underline = None;
553 let mut multi_labels_iter = multi_labels.iter().enumerate().peekable();
554 for label_column in 0..num_multi_labels {
555 match multi_labels_iter.peek() {
556 Some((i, (label_index, ls, label))) if *label_index == label_column => {
557 match label {
558 MultiLabel::Left => {
559 self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?;
560 }
561 MultiLabel::Top(..) if multi_label_index > *i => {
562 self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?;
563 }
564 MultiLabel::Bottom(..) if multi_label_index < *i => {
565 self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?;
566 }
567 MultiLabel::Top(..) if multi_label_index == *i => {
568 underline = Some((*ls, VerticalBound::Top));
569 self.label_multi_top_left(severity, label_style)?
570 }
571 MultiLabel::Bottom(..) if multi_label_index == *i => {
572 underline = Some((*ls, VerticalBound::Bottom));
573 self.label_multi_bottom_left(severity, label_style)?;
574 }
575 MultiLabel::Top(..) | MultiLabel::Bottom(..) => {
576 self.inner_gutter_column(severity, underline)?;
577 }
578 }
579 multi_labels_iter.next();
580 }
581 Some((_, _)) | None => self.inner_gutter_column(severity, underline)?,
582 }
583 }
584
585 match bottom_message {
587 None => self.label_multi_top_caret(severity, label_style, source, *range)?,
588 Some(message) => {
589 self.label_multi_bottom_caret(severity, label_style, source, *range, message)?
590 }
591 }
592 }
593
594 Ok(())
595 }
596
597 pub fn render_snippet_empty(
603 &mut self,
604 outer_padding: usize,
605 severity: Severity,
606 num_multi_labels: usize,
607 multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
608 ) -> Result<(), Error> {
609 self.outer_gutter(outer_padding)?;
610 self.border_left()?;
611 self.inner_gutter(severity, num_multi_labels, multi_labels)?;
612 writeln!(self)?;
613 Ok(())
614 }
615
616 pub fn render_snippet_break(
622 &mut self,
623 outer_padding: usize,
624 severity: Severity,
625 num_multi_labels: usize,
626 multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
627 ) -> Result<(), Error> {
628 self.outer_gutter(outer_padding)?;
629 self.border_left_break()?;
630 self.inner_gutter(severity, num_multi_labels, multi_labels)?;
631 writeln!(self)?;
632 Ok(())
633 }
634
635 pub fn render_snippet_note(
642 &mut self,
643 outer_padding: usize,
644 note: Note,
645 has_another_note_after: bool,
646 ) -> Result<(), Error> {
647 let mut offset = 0;
648 for (note_line_index, line) in note.message.lines().enumerate() {
649 match note_line_index {
650 0 => {
651 self.outer_gutter(outer_padding)?;
652 self.set_color(&self.styles().note_bullet)?;
653 write!(
654 self,
655 "{}",
656 if !has_another_note_after {
657 self.chars().note_bullet_end
658 } else {
659 self.chars().note_bullet_middle
660 }
661 )?;
662 self.reset()?;
663 if let Some(severity) = note.severity {
664 write!(self, " ");
665 if severity == Severity::Note {
666 self.set_color(&self.styles().header_help)?;
667 } else {
668 self.set_color(&self.styles().header(severity))?;
669 }
670 let string = match severity {
671 Severity::Bug => "bug",
672 Severity::Error => "error",
673 Severity::Warning => "warning",
674 Severity::Help => "help",
675 Severity::Note => "note",
676 };
677 offset += string.len();
678 write!(self, "{}", string)?;
679 self.reset()?;
680 write!(self, ":")?;
681 offset += 1;
682 }
683 }
684 _ => {
685 if has_another_note_after {
686 self.outer_gutter(outer_padding)?;
694 self.border_left()?;
695 } else {
696 self.outer_gutter(outer_padding)?;
697 }
698 write!(self, " {}", " ".repeat(offset))?
699 }
700 }
701 writeln!(self, " {}", line)?;
703 }
704
705 Ok(())
706 }
707
708 fn char_metrics(
712 &self,
713 char_indices: impl Iterator<Item = (usize, char)>,
714 ) -> impl Iterator<Item = (Metrics, char)> {
715 use unicode_width::UnicodeWidthChar;
716
717 let tab_width = self.config.tab_width;
718 let mut unicode_column = 0;
719
720 char_indices.map(move |(byte_index, ch)| {
721 let metrics = Metrics {
722 byte_index,
723 unicode_width: match (ch, tab_width) {
724 ('\t', 0) => 0, ('\t', _) => tab_width - (unicode_column % tab_width),
726 (ch, _) => ch.width().unwrap_or(0),
727 },
728 };
729 unicode_column += metrics.unicode_width;
730
731 (metrics, ch)
732 })
733 }
734
735 fn snippet_locus(&mut self, locus: &Locus) -> Result<(), Error> {
737 write!(
738 self,
739 "{name}:{line_number}:{column_number}",
740 name = locus.name,
741 line_number = locus.location.line_number,
742 column_number = locus.location.column_number,
743 )?;
744 Ok(())
745 }
746
747 fn outer_gutter(&mut self, outer_padding: usize) -> Result<(), Error> {
749 write!(self, "{space: >width$} ", space = "", width = outer_padding)?;
750 Ok(())
751 }
752
753 fn outer_gutter_number(
755 &mut self,
756 line_number: usize,
757 outer_padding: usize,
758 ) -> Result<(), Error> {
759 self.set_color(&self.styles().line_number)?;
760 write!(
761 self,
762 "{line_number: >width$}",
763 line_number = line_number,
764 width = outer_padding,
765 )?;
766 self.reset()?;
767 write!(self, " ")?;
768 Ok(())
769 }
770
771 fn border_left(&mut self) -> Result<(), Error> {
773 self.set_color(&self.styles().source_border)?;
774 write!(self, "{}", self.chars().source_border_left)?;
775 self.reset()?;
776 Ok(())
777 }
778
779 fn border_left_break(&mut self) -> Result<(), Error> {
781 self.set_color(&self.styles().source_border)?;
782 write!(self, "{}", self.chars().source_border_left_break)?;
783 self.reset()?;
784 Ok(())
785 }
786
787 fn caret_pointers(
789 &mut self,
790 severity: Severity,
791 max_label_start: usize,
792 single_labels: &[SingleLabel<'_>],
793 trailing_label: Option<(usize, &SingleLabel<'_>)>,
794 char_indices: impl Iterator<Item = (usize, char)>,
795 ) -> Result<(), Error> {
796 for (metrics, ch) in self.char_metrics(char_indices) {
797 let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8());
798 let label_style = hanging_labels(single_labels, trailing_label)
799 .filter(|(_, range, _)| column_range.contains(&range.start))
800 .map(|(label_style, _, _)| *label_style)
801 .max_by_key(label_priority_key);
802
803 let mut spaces = match label_style {
804 None => 0..metrics.unicode_width,
805 Some(label_style) => {
806 self.set_color(self.styles().label(severity, label_style))?;
807 write!(self, "{}", self.chars().pointer_left)?;
808 self.reset()?;
809 1..metrics.unicode_width
810 }
811 };
812 if metrics.byte_index <= max_label_start {
814 spaces.try_for_each(|_| write!(self, " "))?;
815 }
816 }
817
818 Ok(())
819 }
820
821 fn label_multi_left(
827 &mut self,
828 severity: Severity,
829 label_style: LabelStyle,
830 underline: Option<LabelStyle>,
831 ) -> Result<(), Error> {
832 match underline {
833 None => write!(self, " ")?,
834 Some(label_style) => {
836 self.set_color(self.styles().label(severity, label_style))?;
837 write!(self, "{}", self.chars().multi_top)?;
838 self.reset()?;
839 }
840 }
841 self.set_color(self.styles().label(severity, label_style))?;
842 write!(self, "{}", self.chars().multi_left)?;
843 self.reset()?;
844 Ok(())
845 }
846
847 fn label_multi_top_left(
853 &mut self,
854 severity: Severity,
855 label_style: LabelStyle,
856 ) -> Result<(), Error> {
857 write!(self, " ")?;
858 self.set_color(self.styles().label(severity, label_style))?;
859 write!(self, "{}", self.chars().multi_top_left)?;
860 self.reset()?;
861 Ok(())
862 }
863
864 fn label_multi_bottom_left(
870 &mut self,
871 severity: Severity,
872 label_style: LabelStyle,
873 ) -> Result<(), Error> {
874 write!(self, " ")?;
875 self.set_color(self.styles().label(severity, label_style))?;
876 write!(self, "{}", self.chars().multi_bottom_left)?;
877 self.reset()?;
878 Ok(())
879 }
880
881 fn label_multi_top_caret(
887 &mut self,
888 severity: Severity,
889 label_style: LabelStyle,
890 source: &str,
891 start: usize,
892 ) -> Result<(), Error> {
893 self.set_color(self.styles().label(severity, label_style))?;
894
895 for (metrics, _) in self
896 .char_metrics(source.char_indices())
897 .take_while(|(metrics, _)| metrics.byte_index < start + 1)
898 {
899 (0..metrics.unicode_width)
901 .try_for_each(|_| write!(self, "{}", self.chars().multi_top))?;
902 }
903
904 let caret_start = match label_style {
905 LabelStyle::Primary => self.config.chars.multi_primary_caret_start,
906 LabelStyle::Secondary => self.config.chars.multi_secondary_caret_start,
907 };
908 write!(self, "{}", caret_start)?;
909 self.reset()?;
910 writeln!(self)?;
911 Ok(())
912 }
913
914 fn label_multi_bottom_caret(
920 &mut self,
921 severity: Severity,
922 label_style: LabelStyle,
923 source: &str,
924 start: usize,
925 message: &str,
926 ) -> Result<(), Error> {
927 self.set_color(self.styles().label(severity, label_style))?;
928
929 for (metrics, _) in self
930 .char_metrics(source.char_indices())
931 .take_while(|(metrics, _)| metrics.byte_index < start)
932 {
933 (0..metrics.unicode_width)
935 .try_for_each(|_| write!(self, "{}", self.chars().multi_bottom))?;
936 }
937
938 let caret_end = match label_style {
939 LabelStyle::Primary => self.config.chars.multi_primary_caret_start,
940 LabelStyle::Secondary => self.config.chars.multi_secondary_caret_start,
941 };
942 write!(self, "{}", caret_end)?;
943 if !message.is_empty() {
944 write!(self, " {}", message)?;
945 }
946 self.reset()?;
947 writeln!(self)?;
948 Ok(())
949 }
950
951 fn inner_gutter_column(
953 &mut self,
954 severity: Severity,
955 underline: Option<Underline>,
956 ) -> Result<(), Error> {
957 match underline {
958 None => self.inner_gutter_space(),
959 Some((label_style, vertical_bound)) => {
960 self.set_color(self.styles().label(severity, label_style))?;
961 let ch = match vertical_bound {
962 VerticalBound::Top => self.config.chars.multi_top,
963 VerticalBound::Bottom => self.config.chars.multi_bottom,
964 };
965 write!(self, "{0}{0}", ch)?;
966 self.reset()?;
967 Ok(())
968 }
969 }
970 }
971
972 fn inner_gutter_space(&mut self) -> Result<(), Error> {
974 write!(self, " ")?;
975 Ok(())
976 }
977
978 fn inner_gutter(
980 &mut self,
981 severity: Severity,
982 num_multi_labels: usize,
983 multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
984 ) -> Result<(), Error> {
985 let mut multi_labels_iter = multi_labels.iter().peekable();
986 for label_column in 0..num_multi_labels {
987 match multi_labels_iter.peek() {
988 Some((label_index, ls, label)) if *label_index == label_column => match label {
989 MultiLabel::Left | MultiLabel::Bottom(..) => {
990 self.label_multi_left(severity, *ls, None)?;
991 multi_labels_iter.next();
992 }
993 MultiLabel::Top(..) => {
994 self.inner_gutter_space()?;
995 multi_labels_iter.next();
996 }
997 },
998 Some((_, _, _)) | None => self.inner_gutter_space()?,
999 }
1000 }
1001
1002 Ok(())
1003 }
1004}
1005
1006impl<'writer, 'config> Write for Renderer<'writer, 'config> {
1007 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
1008 self.writer.write(buf)
1009 }
1010
1011 fn flush(&mut self) -> io::Result<()> {
1012 self.writer.flush()
1013 }
1014}
1015
1016impl<'writer, 'config> WriteColor for Renderer<'writer, 'config> {
1017 fn supports_color(&self) -> bool {
1018 self.writer.supports_color()
1019 }
1020
1021 fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
1022 self.writer.set_color(spec)
1023 }
1024
1025 fn reset(&mut self) -> io::Result<()> {
1026 self.writer.reset()
1027 }
1028
1029 fn is_synchronous(&self) -> bool {
1030 self.writer.is_synchronous()
1031 }
1032}
1033
1034struct Metrics {
1035 byte_index: usize,
1036 unicode_width: usize,
1037}
1038
1039fn is_overlapping(range0: &Range<usize>, range1: &Range<usize>) -> bool {
1041 let start = std::cmp::max(range0.start, range1.start);
1042 let end = std::cmp::min(range0.end, range1.end);
1043 start < end
1044}
1045
1046fn label_priority_key(label_style: &LabelStyle) -> u8 {
1048 match label_style {
1049 LabelStyle::Secondary => 0,
1050 LabelStyle::Primary => 1,
1051 }
1052}
1053
1054fn hanging_labels<'labels, 'diagnostic>(
1057 single_labels: &'labels [SingleLabel<'diagnostic>],
1058 trailing_label: Option<(usize, &'labels SingleLabel<'diagnostic>)>,
1059) -> impl 'labels + DoubleEndedIterator<Item = &'labels SingleLabel<'diagnostic>> {
1060 single_labels
1061 .iter()
1062 .enumerate()
1063 .filter(|(_, (_, _, message))| !message.is_empty())
1064 .filter(move |(i, _)| trailing_label.map_or(true, |(j, _)| *i != j))
1065 .map(|(_, label)| label)
1066}