1use std::{
12 borrow::Cow,
13 cmp::{min, Reverse},
14 collections::HashMap,
15 io::{self, prelude::*},
16};
17
18#[cfg(feature = "tty-emitter")]
19use termcolor::{Buffer, BufferWriter, Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
20
21use self::Destination::*;
22use super::{
23 diagnostic::Message,
24 snippet::{Annotation, AnnotationType, Line, MultilineAnnotation, Style, StyledString},
25 styled_buffer::StyledBuffer,
26 CodeSuggestion, DiagnosticBuilder, DiagnosticId, Level, SourceMapperDyn, SubDiagnostic,
27};
28use crate::{
29 sync::Lrc,
30 syntax_pos::{MultiSpan, SourceFile, Span},
31};
32
33const ANONYMIZED_LINE_NUM: &str = "LL";
34
35pub trait Emitter: crate::sync::Send {
37 fn emit(&mut self, db: &DiagnosticBuilder<'_>);
39
40 fn should_show_explain(&self) -> bool {
42 true
43 }
44
45 fn take_diagnostics(&mut self) -> Vec<String> {
46 vec![]
47 }
48}
49
50impl Emitter for EmitterWriter {
51 fn emit(&mut self, db: &DiagnosticBuilder<'_>) {
52 let mut primary_span = db.span.clone();
53 let mut children = db.children.clone();
54 let mut suggestions: &[_] = &[];
55
56 if let Some((sugg, rest)) = db.suggestions.split_first() {
57 if rest.is_empty() &&
58 sugg.substitutions.len() == 1 &&
60 sugg.substitutions[0].parts.len() == 1 &&
62 sugg.msg.split_whitespace().count() < 10 &&
64 !sugg.substitutions[0].parts[0].snippet.contains('\n')
66 {
67 let substitution = &sugg.substitutions[0].parts[0].snippet.trim();
68 let msg = if substitution.is_empty() || !sugg.show_code_when_inline {
69 format!("help: {}", sugg.msg)
72 } else {
73 format!("help: {}: `{}`", sugg.msg, substitution)
74 };
75 primary_span.push_span_label(sugg.substitutions[0].parts[0].span, msg);
76 } else {
77 suggestions = &db.suggestions;
82 }
83 }
84
85 self.fix_multispans_in_std_macros(
86 &mut primary_span,
87 &mut children,
88 db.handler.flags.external_macro_backtrace,
89 );
90
91 self.emit_messages_default(
92 db.level,
93 db.styled_message(),
94 &db.code,
95 &primary_span,
96 &children,
97 suggestions,
98 );
99 }
100
101 fn should_show_explain(&self) -> bool {
102 !self.short_message
103 }
104}
105
106pub const MAX_HIGHLIGHT_LINES: usize = 6;
108pub const MAX_SUGGESTIONS: usize = 4;
112
113#[derive(Clone, Copy, Debug, PartialEq, Eq)]
114pub enum ColorConfig {
115 Auto,
116 Always,
117 Never,
118}
119
120impl ColorConfig {
121 #[cfg(feature = "tty-emitter")]
122 fn to_color_choice(self) -> ColorChoice {
123 use std::io::IsTerminal;
124
125 let stderr = io::stderr();
126
127 match self {
128 ColorConfig::Always => {
129 if stderr.is_terminal() {
130 ColorChoice::Always
131 } else {
132 ColorChoice::AlwaysAnsi
133 }
134 }
135 ColorConfig::Never => ColorChoice::Never,
136 ColorConfig::Auto if stderr.is_terminal() => ColorChoice::Auto,
137 ColorConfig::Auto => ColorChoice::Never,
138 }
139 }
140}
141
142pub struct EmitterWriter {
144 dst: Destination,
145 sm: Option<Lrc<SourceMapperDyn>>,
146 short_message: bool,
147 teach: bool,
148 ui_testing: bool,
149
150 skip_filename: bool,
151}
152
153struct FileWithAnnotatedLines {
154 file: Lrc<SourceFile>,
155 lines: Vec<Line>,
156 multiline_depth: usize,
157}
158
159#[cfg(feature = "tty-emitter")]
160#[cfg_attr(docsrs, doc(cfg(feature = "tty-emitter")))]
161impl EmitterWriter {
162 pub fn stderr(
163 color_config: ColorConfig,
164 source_map: Option<Lrc<SourceMapperDyn>>,
165 short_message: bool,
166 teach: bool,
167 ) -> EmitterWriter {
168 let dst = Destination::from_stderr(color_config);
169 EmitterWriter {
170 dst,
171 sm: source_map,
172 short_message,
173 teach,
174 ui_testing: false,
175 skip_filename: false,
176 }
177 }
178}
179
180impl EmitterWriter {
181 pub fn new(
182 dst: Box<dyn Write + Send>,
183 source_map: Option<Lrc<SourceMapperDyn>>,
184 short_message: bool,
185 teach: bool,
186 ) -> EmitterWriter {
187 EmitterWriter {
188 dst: Raw(dst),
189 sm: source_map,
190 short_message,
191 teach,
192 ui_testing: false,
193 skip_filename: false,
194 }
195 }
196
197 pub fn ui_testing(mut self, ui_testing: bool) -> Self {
198 self.ui_testing = ui_testing;
199 self
200 }
201
202 pub fn skip_filename(mut self, skip_filename: bool) -> Self {
203 self.skip_filename = skip_filename;
204 self
205 }
206
207 fn maybe_anonymized(&self, line_num: usize) -> String {
208 if self.ui_testing {
209 ANONYMIZED_LINE_NUM.to_string()
210 } else {
211 line_num.to_string()
212 }
213 }
214
215 fn preprocess_annotations(&mut self, msp: &MultiSpan) -> Vec<FileWithAnnotatedLines> {
216 fn add_annotation_to_file(
217 file_vec: &mut Vec<FileWithAnnotatedLines>,
218 file: Lrc<SourceFile>,
219 line_index: usize,
220 ann: Annotation,
221 ) {
222 for slot in file_vec.iter_mut() {
223 if slot.file.name == file.name {
225 for line_slot in &mut slot.lines {
227 if line_slot.line_index == line_index {
228 line_slot.annotations.push(ann);
229 return;
230 }
231 }
232 slot.lines.push(Line {
234 line_index,
235 annotations: vec![ann],
236 });
237 slot.lines.sort();
238 return;
239 }
240 }
241 file_vec.push(FileWithAnnotatedLines {
243 file,
244 lines: vec![Line {
245 line_index,
246 annotations: vec![ann],
247 }],
248 multiline_depth: 0,
249 });
250 }
251
252 let mut output = Vec::new();
253 let mut multiline_annotations = Vec::new();
254
255 if let Some(ref sm) = self.sm {
256 for span_label in msp.span_labels() {
257 if span_label.span.is_dummy() {
258 continue;
259 }
260
261 let lo = sm.lookup_char_pos(span_label.span.lo());
262 let mut hi = sm.lookup_char_pos(span_label.span.hi());
263
264 if lo.col_display == hi.col_display && lo.line == hi.line {
270 hi.col_display += 1;
271 }
272
273 let ann_type = if lo.line != hi.line {
274 let ml = MultilineAnnotation {
275 depth: 1,
276 line_start: lo.line,
277 line_end: hi.line,
278 start_col: lo.col_display,
279 end_col: hi.col_display,
280 is_primary: span_label.is_primary,
281 label: span_label.label.clone(),
282 };
283 multiline_annotations.push((lo.file.clone(), ml.clone()));
284 AnnotationType::Multiline(ml)
285 } else {
286 AnnotationType::Singleline
287 };
288 let ann = Annotation {
289 start_col: lo.col_display,
290 end_col: hi.col_display,
291 is_primary: span_label.is_primary,
292 label: span_label.label.clone(),
293 annotation_type: ann_type,
294 };
295
296 if !ann.is_multiline() {
297 add_annotation_to_file(&mut output, lo.file, lo.line, ann);
298 }
299 }
300 }
301
302 multiline_annotations.sort_by_key(|(_, ml)| (ml.line_start, ml.line_end));
304 for item in multiline_annotations.clone() {
305 let ann = item.1;
306 for item in multiline_annotations.iter_mut() {
307 let a = &mut item.1;
308 if &ann != a
311 && num_overlap(ann.line_start, ann.line_end, a.line_start, a.line_end, true)
312 {
313 a.increase_depth();
314 } else {
315 break;
316 }
317 }
318 }
319
320 let mut max_depth = 0; for (file, ann) in multiline_annotations {
322 if ann.depth > max_depth {
323 max_depth = ann.depth;
324 }
325 add_annotation_to_file(&mut output, file.clone(), ann.line_start, ann.as_start());
326 let middle = min(ann.line_start + 4, ann.line_end);
327 for line in ann.line_start + 1..middle {
328 add_annotation_to_file(&mut output, file.clone(), line, ann.as_line());
329 }
330 if middle < ann.line_end - 1 {
331 for line in ann.line_end - 1..ann.line_end {
332 add_annotation_to_file(&mut output, file.clone(), line, ann.as_line());
333 }
334 }
335 add_annotation_to_file(&mut output, file, ann.line_end, ann.as_end());
336 }
337 for file_vec in output.iter_mut() {
338 file_vec.multiline_depth = max_depth;
339 }
340 output
341 }
342
343 #[allow(clippy::cognitive_complexity)]
344 fn render_source_line(
345 &self,
346 buffer: &mut StyledBuffer,
347 file: Lrc<SourceFile>,
348 line: &Line,
349 width_offset: usize,
350 code_offset: usize,
351 ) -> Vec<(usize, Style)> {
352 if line.line_index == 0 {
353 return Vec::new();
354 }
355
356 let source_string = match file.get_line(line.line_index - 1) {
357 Some(s) => s,
358 None => return Vec::new(),
359 };
360
361 let line_offset = buffer.num_lines();
362
363 buffer.puts(line_offset, code_offset, &source_string, Style::Quotation);
365 buffer.puts(
366 line_offset,
367 0,
368 &self.maybe_anonymized(line.line_index),
369 Style::LineNumber,
370 );
371
372 draw_col_separator(buffer, line_offset, width_offset - 2);
373
374 if line.annotations.len() == 1 {
391 if let Some(ann) = line.annotations.first() {
392 if let AnnotationType::MultilineStart(depth) = ann.annotation_type {
393 if source_string
394 .chars()
395 .take(ann.start_col)
396 .all(|c| c.is_whitespace())
397 {
398 let style = if ann.is_primary {
399 Style::UnderlinePrimary
400 } else {
401 Style::UnderlineSecondary
402 };
403 buffer.putc(line_offset, width_offset + depth - 1, '/', style);
404 return vec![(depth, style)];
405 }
406 }
407 }
408 }
409
410 let mut annotations = line.annotations.clone();
443 annotations.sort_by_key(|a| Reverse(a.start_col));
444
445 let mut annotations_position = Vec::new();
508 let mut line_len = 0;
509 let mut p = 0;
510 for (i, annotation) in annotations.iter().enumerate() {
511 for (j, next) in annotations.iter().enumerate() {
512 if overlaps(next, annotation, 0) && annotation.has_label() && j > i && p == 0
516 {
518 p += 1;
520 break;
521 }
522 }
523 annotations_position.push((p, annotation));
524 for (j, next) in annotations.iter().enumerate() {
525 if j > i {
526 let l = if let Some(ref label) = next.label {
527 label.len() + 2
528 } else {
529 0
530 };
531 if (overlaps(next, annotation, l) && annotation.has_label() && next.has_label()) || (annotation.takes_space() && next.has_label()) || (annotation.has_label() && next.takes_space())
548 || (annotation.takes_space() && next.takes_space())
549 || (overlaps(next, annotation, l)
550 && next.end_col <= annotation.end_col
551 && next.has_label()
552 && p == 0)
553 {
555 p += 1;
557 break;
558 }
559 }
560 }
561 if line_len < p {
562 line_len = p;
563 }
564 }
565
566 if line_len != 0 {
567 line_len += 1;
568 }
569
570 if line.annotations.iter().all(|a| a.is_line()) {
573 return Vec::new();
574 }
575
576 for pos in 0..=line_len {
588 draw_col_separator(buffer, line_offset + pos + 1, width_offset - 2);
589 buffer.putc(
590 line_offset + pos + 1,
591 width_offset - 2,
592 '|',
593 Style::LineNumber,
594 );
595 }
596
597 for &(pos, annotation) in &annotations_position {
610 let style = if annotation.is_primary {
611 Style::UnderlinePrimary
612 } else {
613 Style::UnderlineSecondary
614 };
615 let pos = pos + 1;
616 match annotation.annotation_type {
617 AnnotationType::MultilineStart(depth) | AnnotationType::MultilineEnd(depth) => {
618 draw_range(
619 buffer,
620 '_',
621 line_offset + pos,
622 width_offset + depth,
623 code_offset + annotation.start_col,
624 style,
625 );
626 }
627 _ if self.teach => {
628 buffer.set_style_range(
629 line_offset,
630 code_offset + annotation.start_col,
631 code_offset + annotation.end_col,
632 style,
633 annotation.is_primary,
634 );
635 }
636 _ => {}
637 }
638 }
639
640 for &(pos, annotation) in &annotations_position {
653 let style = if annotation.is_primary {
654 Style::UnderlinePrimary
655 } else {
656 Style::UnderlineSecondary
657 };
658 let pos = pos + 1;
659
660 if pos > 1 && (annotation.has_label() || annotation.takes_space()) {
661 for p in line_offset + 1..=line_offset + pos {
662 buffer.putc(p, code_offset + annotation.start_col, '|', style);
663 }
664 }
665 match annotation.annotation_type {
666 AnnotationType::MultilineStart(depth) => {
667 for p in line_offset + pos + 1..line_offset + line_len + 2 {
668 buffer.putc(p, width_offset + depth - 1, '|', style);
669 }
670 }
671 AnnotationType::MultilineEnd(depth) => {
672 for p in line_offset..=line_offset + pos {
673 buffer.putc(p, width_offset + depth - 1, '|', style);
674 }
675 }
676 _ => (),
677 }
678 }
679
680 for &(pos, annotation) in &annotations_position {
692 let style = if annotation.is_primary {
693 Style::LabelPrimary
694 } else {
695 Style::LabelSecondary
696 };
697 let (pos, col) = if pos == 0 {
698 (pos + 1, annotation.end_col + 1)
699 } else {
700 (pos + 2, annotation.start_col)
701 };
702 if let Some(ref label) = annotation.label {
703 buffer.puts(line_offset + pos, code_offset + col, label, style);
704 }
705 }
706
707 annotations_position.sort_by(|a, b| {
716 a.1.len().cmp(&b.1.len()).reverse()
718 });
719
720 for &(_, annotation) in &annotations_position {
732 let (underline, style) = if annotation.is_primary {
733 ('^', Style::UnderlinePrimary)
734 } else {
735 ('-', Style::UnderlineSecondary)
736 };
737 for p in annotation.start_col..annotation.end_col {
738 buffer.putc(line_offset + 1, code_offset + p, underline, style);
739 }
740 }
741 annotations_position
742 .iter()
743 .filter_map(|&(_, annotation)| match annotation.annotation_type {
744 AnnotationType::MultilineStart(p) | AnnotationType::MultilineEnd(p) => {
745 let style = if annotation.is_primary {
746 Style::LabelPrimary
747 } else {
748 Style::LabelSecondary
749 };
750 Some((p, style))
751 }
752 _ => None,
753 })
754 .collect::<Vec<_>>()
755 }
756
757 fn get_multispan_max_line_num(&mut self, msp: &MultiSpan) -> usize {
758 let mut max = 0;
759 if let Some(ref sm) = self.sm {
760 for primary_span in msp.primary_spans() {
761 if !primary_span.is_dummy() {
762 let hi = sm.lookup_char_pos(primary_span.hi());
763 if hi.line > max {
764 max = hi.line;
765 }
766 }
767 }
768 if !self.short_message {
769 for span_label in msp.span_labels() {
770 if !span_label.span.is_dummy() {
771 let hi = sm.lookup_char_pos(span_label.span.hi());
772 if hi.line > max {
773 max = hi.line;
774 }
775 }
776 }
777 }
778 }
779 max
780 }
781
782 fn get_max_line_num(&mut self, span: &MultiSpan, children: &[SubDiagnostic]) -> usize {
783 let primary = self.get_multispan_max_line_num(span);
784 children
785 .iter()
786 .map(|sub| self.get_multispan_max_line_num(&sub.span))
787 .max()
788 .unwrap_or(0)
789 .max(primary)
790 }
791
792 fn fix_multispan_in_std_macros(
796 &mut self,
797 span: &mut MultiSpan,
798 always_backtrace: bool,
799 ) -> bool {
800 let mut spans_updated = false;
801
802 if let Some(ref sm) = self.sm {
803 let mut before_after: Vec<(Span, Span)> = Vec::new();
804 let new_labels: Vec<(Span, String)> = Vec::new();
805
806 for sp in span.primary_spans() {
808 if sp.is_dummy() {
809 continue;
810 }
811 let call_sp = sm.call_span_if_macro(*sp);
812 if call_sp != *sp && !always_backtrace {
813 before_after.push((*sp, call_sp));
814 }
815 }
816 for (label_span, label_text) in new_labels {
817 span.push_span_label(label_span, label_text);
818 }
819 for sp_label in span.span_labels() {
820 if sp_label.span.is_dummy() {
821 continue;
822 }
823 }
824 for (before, after) in before_after {
827 span.replace(before, after);
828 spans_updated = true;
829 }
830 }
831
832 spans_updated
833 }
834
835 fn fix_multispans_in_std_macros(
839 &mut self,
840 span: &mut MultiSpan,
841 children: &mut Vec<SubDiagnostic>,
842 backtrace: bool,
843 ) {
844 let mut spans_updated = self.fix_multispan_in_std_macros(span, backtrace);
845 for child in children.iter_mut() {
846 spans_updated |= self.fix_multispan_in_std_macros(&mut child.span, backtrace);
847 }
848 if spans_updated {
849 children.push(SubDiagnostic {
850 level: Level::Note,
851 message: vec![Message(
852 "this error originates in a macro outside of the current crate (in Nightly \
853 builds, run with -Z external-macro-backtrace for more info)"
854 .to_string(),
855 Style::NoStyle,
856 )],
857 span: MultiSpan::new(),
858 render_span: None,
859 });
860 }
861 }
862
863 fn msg_to_buffer(
866 &self,
867 buffer: &mut StyledBuffer,
868 msg: &[Message],
869 padding: usize,
870 label: &str,
871 override_style: Option<Style>,
872 ) {
873 let padding = " ".repeat(padding + label.len() + 5);
890
891 fn style_or_override(style: Style, override_: Option<Style>) -> Style {
894 match (style, override_) {
895 (Style::NoStyle, Some(override_)) => override_,
896 _ => style,
897 }
898 }
899
900 let mut line_number = 0;
901
902 for Message(text, ref style) in msg.iter() {
922 let lines = text.split('\n').collect::<Vec<_>>();
923 if lines.len() > 1 {
924 for (i, line) in lines.iter().enumerate() {
925 if i != 0 {
926 line_number += 1;
927 buffer.append(line_number, &padding, Style::NoStyle);
928 }
929 buffer.append(line_number, line, style_or_override(*style, override_style));
930 }
931 } else {
932 buffer.append(line_number, text, style_or_override(*style, override_style));
933 }
934 }
935 }
936
937 #[allow(clippy::cognitive_complexity, clippy::comparison_chain)]
938 fn emit_message_default(
939 &mut self,
940 msp: &MultiSpan,
941 msg: &[Message],
942 code: &Option<DiagnosticId>,
943 level: Level,
944 max_line_num_len: usize,
945 is_secondary: bool,
946 ) -> io::Result<()> {
947 let mut buffer = StyledBuffer::new();
948 let header_style = if is_secondary {
949 Style::HeaderMsg
950 } else {
951 Style::MainHeaderMsg
952 };
953
954 if msp.primary_spans().is_empty()
955 && msp.span_labels().is_empty()
956 && is_secondary
957 && !self.short_message
958 {
959 for _ in 0..max_line_num_len {
961 buffer.prepend(0, " ", Style::NoStyle);
962 }
963 draw_note_separator(&mut buffer, 0, max_line_num_len + 1);
964 let level_str = level.to_string();
965 if !level_str.is_empty() {
966 buffer.append(0, &level_str, Style::MainHeaderMsg);
967 buffer.append(0, ": ", Style::NoStyle);
968 }
969 self.msg_to_buffer(&mut buffer, msg, max_line_num_len, "note", None);
970 } else {
971 let level_str = level.to_string();
972 if !level_str.is_empty() {
973 buffer.append(0, &level_str, Style::Level(level));
974 }
975 if let Some(DiagnosticId::Error(ref code)) = *code {
977 buffer.append(0, "[", Style::Level(level));
978 buffer.append(0, code, Style::Level(level));
979 buffer.append(0, "]", Style::Level(level));
980 }
981 if !level_str.is_empty() {
982 buffer.append(0, ": ", header_style);
983 }
984 for Message(text, _) in msg.iter() {
985 buffer.append(0, text, header_style);
986 }
987 }
988
989 let mut annotated_files = self.preprocess_annotations(msp);
993
994 let (primary_lo, sm) = if let (Some(sm), Some(primary_span)) =
996 (self.sm.as_ref(), msp.primary_span().as_ref())
997 {
998 if !primary_span.is_dummy() {
999 (sm.lookup_char_pos(primary_span.lo()), sm)
1000 } else {
1001 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
1002 return Ok(());
1003 }
1004 } else {
1005 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
1007 return Ok(());
1008 };
1009 if let Ok(pos) =
1010 annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
1011 {
1012 annotated_files.swap(0, pos);
1013 }
1014
1015 for annotated_file in annotated_files {
1017 let is_primary = primary_lo.file.name == annotated_file.file.name;
1020 if is_primary {
1021 let loc = primary_lo.clone();
1022 if !self.short_message {
1023 let buffer_msg_line_offset = buffer.num_lines();
1025
1026 if !self.skip_filename {
1027 buffer.prepend(buffer_msg_line_offset, "--> ", Style::LineNumber);
1028
1029 buffer.append(
1030 buffer_msg_line_offset,
1031 &format!(
1032 "{}:{}:{}",
1033 loc.file.name,
1034 sm.doctest_offset_line(loc.line),
1035 loc.col.0 + 1
1036 ),
1037 Style::LineAndColumn,
1038 );
1039 }
1040
1041 for _ in 0..max_line_num_len {
1042 buffer.prepend(buffer_msg_line_offset, " ", Style::NoStyle);
1043 }
1044 } else {
1045 buffer.prepend(
1046 0,
1047 &format!(
1048 "{}:{}:{}: ",
1049 loc.file.name,
1050 sm.doctest_offset_line(loc.line),
1051 loc.col.0 + 1
1052 ),
1053 Style::LineAndColumn,
1054 );
1055 }
1056 } else if !self.short_message {
1057 let buffer_msg_line_offset = buffer.num_lines();
1059
1060 draw_col_separator(&mut buffer, buffer_msg_line_offset, max_line_num_len + 1);
1062
1063 buffer.prepend(buffer_msg_line_offset + 1, "::: ", Style::LineNumber);
1065 let loc = if let Some(first_line) = annotated_file.lines.first() {
1066 let col = if let Some(first_annotation) = first_line.annotations.first() {
1067 format!(":{}", first_annotation.start_col + 1)
1068 } else {
1069 String::new()
1070 };
1071 format!(
1072 "{}:{}{}",
1073 annotated_file.file.name,
1074 sm.doctest_offset_line(first_line.line_index),
1075 col
1076 )
1077 } else {
1078 annotated_file.file.name.to_string()
1079 };
1080 buffer.append(buffer_msg_line_offset + 1, &loc, Style::LineAndColumn);
1081 for _ in 0..max_line_num_len {
1082 buffer.prepend(buffer_msg_line_offset + 1, " ", Style::NoStyle);
1083 }
1084 }
1085
1086 if !self.short_message {
1087 let buffer_msg_line_offset = buffer.num_lines();
1089 draw_col_separator_no_space(
1090 &mut buffer,
1091 buffer_msg_line_offset,
1092 max_line_num_len + 1,
1093 );
1094
1095 let mut multilines = HashMap::<_, _>::default();
1097
1098 for line_idx in 0..annotated_file.lines.len() {
1100 let previous_buffer_line = buffer.num_lines();
1101
1102 let width_offset = 3 + max_line_num_len;
1103 let code_offset = if annotated_file.multiline_depth == 0 {
1104 width_offset
1105 } else {
1106 width_offset + annotated_file.multiline_depth + 1
1107 };
1108
1109 let depths = self.render_source_line(
1110 &mut buffer,
1111 annotated_file.file.clone(),
1112 &annotated_file.lines[line_idx],
1113 width_offset,
1114 code_offset,
1115 );
1116
1117 let mut to_add = HashMap::<_, _>::default();
1118
1119 for (depth, style) in depths {
1120 if multilines.contains_key(&depth) {
1121 multilines.remove(&depth);
1122 } else {
1123 to_add.insert(depth, style);
1124 }
1125 }
1126
1127 for (depth, style) in &multilines {
1130 for line in previous_buffer_line..buffer.num_lines() {
1131 draw_multiline_line(&mut buffer, line, width_offset, *depth, *style);
1132 }
1133 }
1134 if line_idx < (annotated_file.lines.len() - 1) {
1137 let line_idx_delta = annotated_file.lines[line_idx + 1].line_index
1138 - annotated_file.lines[line_idx].line_index;
1139 if line_idx_delta > 2 {
1140 let last_buffer_line_num = buffer.num_lines();
1141 buffer.puts(last_buffer_line_num, 0, "...", Style::LineNumber);
1142
1143 for (depth, style) in &multilines {
1145 draw_multiline_line(
1146 &mut buffer,
1147 last_buffer_line_num,
1148 width_offset,
1149 *depth,
1150 *style,
1151 );
1152 }
1153 } else if line_idx_delta == 2 {
1154 let unannotated_line = annotated_file
1155 .file
1156 .get_line(annotated_file.lines[line_idx].line_index)
1157 .unwrap_or_else(|| Cow::from(""));
1158
1159 let last_buffer_line_num = buffer.num_lines();
1160
1161 buffer.puts(
1162 last_buffer_line_num,
1163 0,
1164 &self.maybe_anonymized(
1165 annotated_file.lines[line_idx + 1].line_index - 1,
1166 ),
1167 Style::LineNumber,
1168 );
1169 draw_col_separator(
1170 &mut buffer,
1171 last_buffer_line_num,
1172 1 + max_line_num_len,
1173 );
1174 buffer.puts(
1175 last_buffer_line_num,
1176 code_offset,
1177 &unannotated_line,
1178 Style::Quotation,
1179 );
1180
1181 for (depth, style) in &multilines {
1182 draw_multiline_line(
1183 &mut buffer,
1184 last_buffer_line_num,
1185 width_offset,
1186 *depth,
1187 *style,
1188 );
1189 }
1190 }
1191 }
1192
1193 multilines.extend(&to_add);
1194 }
1195 }
1196 }
1197
1198 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
1200
1201 Ok(())
1202 }
1203
1204 fn emit_suggestion_default(
1205 &mut self,
1206 suggestion: &CodeSuggestion,
1207 level: Level,
1208 max_line_num_len: usize,
1209 ) -> io::Result<()> {
1210 if let Some(ref sm) = self.sm {
1211 let mut buffer = StyledBuffer::new();
1212
1213 let level_str = level.to_string();
1215 if !level_str.is_empty() {
1216 buffer.append(0, &level_str, Style::Level(level));
1217 buffer.append(0, ": ", Style::HeaderMsg);
1218 }
1219 self.msg_to_buffer(
1220 &mut buffer,
1221 &[Message(suggestion.msg.to_owned(), Style::NoStyle)],
1222 max_line_num_len,
1223 "suggestion",
1224 Some(Style::HeaderMsg),
1225 );
1226
1227 let suggestions = suggestion.splice_lines(&**sm);
1229
1230 let mut row_num = 2;
1231 for (complete, parts) in suggestions.iter().take(MAX_SUGGESTIONS) {
1232 let show_underline = !(parts.len() == 1
1236 && parts[0].snippet.trim() == complete.trim())
1237 && complete.lines().count() == 1;
1238
1239 let lines = sm.span_to_lines(parts[0].span).unwrap();
1240
1241 assert!(!lines.lines.is_empty());
1242
1243 let line_start = sm.lookup_char_pos(parts[0].span.lo()).line;
1244 draw_col_separator_no_space(&mut buffer, 1, max_line_num_len + 1);
1245 let mut lines = complete.lines();
1246 for (line_pos, line) in lines.by_ref().take(MAX_HIGHLIGHT_LINES).enumerate() {
1247 buffer.puts(
1249 row_num,
1250 0,
1251 &self.maybe_anonymized(line_start + line_pos),
1252 Style::LineNumber,
1253 );
1254 draw_col_separator(&mut buffer, row_num, max_line_num_len + 1);
1256 buffer.append(row_num, line, Style::NoStyle);
1257 row_num += 1;
1258 }
1259
1260 let mut offset: isize = 0;
1263 if show_underline {
1266 draw_col_separator(&mut buffer, row_num, max_line_num_len + 1);
1267 for part in parts {
1268 let span_start_pos = sm.lookup_char_pos(part.span.lo()).col_display;
1269 let span_end_pos = sm.lookup_char_pos(part.span.hi()).col_display;
1270
1271 let start = part
1273 .snippet
1274 .len()
1275 .saturating_sub(part.snippet.trim_start().len());
1276 let sub_len = part.snippet.trim().chars().fold(0, |acc, ch| {
1279 acc + unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0)
1280 });
1281
1282 let underline_start = (span_start_pos + start) as isize + offset;
1283 let underline_end = (span_start_pos + start + sub_len) as isize + offset;
1284 for p in underline_start..underline_end {
1285 buffer.putc(
1286 row_num,
1287 max_line_num_len + 3 + p as usize,
1288 '^',
1289 Style::UnderlinePrimary,
1290 );
1291 }
1292 if underline_start == underline_end {
1294 for p in underline_start - 1..=underline_start {
1295 buffer.putc(
1296 row_num,
1297 max_line_num_len + 3 + p as usize,
1298 '-',
1299 Style::UnderlineSecondary,
1300 );
1301 }
1302 }
1303
1304 let full_sub_len = part.snippet.chars().fold(0, |acc, ch| {
1306 acc + unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0) as isize
1307 });
1308
1309 let snippet_len = span_end_pos as isize - span_start_pos as isize;
1311 offset += full_sub_len - snippet_len;
1314 }
1315 row_num += 1;
1316 }
1317
1318 if lines.next().is_some() {
1320 buffer.puts(row_num, max_line_num_len - 1, "...", Style::LineNumber);
1321 } else if !show_underline {
1322 draw_col_separator_no_space(&mut buffer, row_num, max_line_num_len + 1);
1323 row_num += 1;
1324 }
1325 }
1326 if suggestions.len() > MAX_SUGGESTIONS {
1327 let msg = format!(
1328 "and {} other candidates",
1329 suggestions.len() - MAX_SUGGESTIONS
1330 );
1331 buffer.puts(row_num, 0, &msg, Style::NoStyle);
1332 }
1333 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
1334 }
1335 Ok(())
1336 }
1337
1338 fn emit_messages_default(
1339 &mut self,
1340 level: Level,
1341 message: &[Message],
1342 code: &Option<DiagnosticId>,
1343 span: &MultiSpan,
1344 children: &[SubDiagnostic],
1345 suggestions: &[CodeSuggestion],
1346 ) {
1347 let max_line_num_len = if self.ui_testing {
1348 ANONYMIZED_LINE_NUM.len()
1349 } else {
1350 self.get_max_line_num(span, children).to_string().len()
1351 };
1352
1353 match self.emit_message_default(span, message, code, level, max_line_num_len, false) {
1354 Ok(()) => {
1355 if !children.is_empty() {
1356 let mut buffer = StyledBuffer::new();
1357 if !self.short_message {
1358 draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1);
1359 }
1360 match emit_to_destination(
1361 &buffer.render(),
1362 level,
1363 &mut self.dst,
1364 self.short_message,
1365 ) {
1366 Ok(()) => (),
1367 Err(e) => panic!("failed to emit error: {}", e),
1368 }
1369 }
1370 if !self.short_message {
1371 for child in children {
1372 let span = child.render_span.as_ref().unwrap_or(&child.span);
1373 if let Err(e) = self.emit_message_default(
1374 span,
1375 child.styled_message(),
1376 &None,
1377 child.level,
1378 max_line_num_len,
1379 true,
1380 ) {
1381 panic!("failed to emit error: {}", e)
1382 }
1383 }
1384 for sugg in suggestions {
1385 if let Err(e) =
1386 self.emit_suggestion_default(sugg, Level::Help, max_line_num_len)
1387 {
1388 panic!("failed to emit error: {}", e)
1389 }
1390 }
1391 }
1392 }
1393 Err(e) => panic!("failed to emit error: {}", e),
1394 }
1395
1396 let mut dst = self.dst.writable();
1397 if let Err(e) = writeln!(dst).and_then(|_| dst.flush()) {
1398 panic!("failed to emit error: {}", e)
1399 }
1400 }
1401}
1402
1403fn draw_col_separator(buffer: &mut StyledBuffer, line: usize, col: usize) {
1404 buffer.puts(line, col, "| ", Style::LineNumber);
1405}
1406
1407fn draw_col_separator_no_space(buffer: &mut StyledBuffer, line: usize, col: usize) {
1408 draw_col_separator_no_space_with_style(buffer, line, col, Style::LineNumber);
1409}
1410
1411fn draw_col_separator_no_space_with_style(
1412 buffer: &mut StyledBuffer,
1413 line: usize,
1414 col: usize,
1415 style: Style,
1416) {
1417 buffer.putc(line, col, '|', style);
1418}
1419
1420fn draw_range(
1421 buffer: &mut StyledBuffer,
1422 symbol: char,
1423 line: usize,
1424 col_from: usize,
1425 col_to: usize,
1426 style: Style,
1427) {
1428 for col in col_from..col_to {
1429 buffer.putc(line, col, symbol, style);
1430 }
1431}
1432
1433fn draw_note_separator(buffer: &mut StyledBuffer, line: usize, col: usize) {
1434 buffer.puts(line, col, "= ", Style::LineNumber);
1435}
1436
1437fn draw_multiline_line(
1438 buffer: &mut StyledBuffer,
1439 line: usize,
1440 offset: usize,
1441 depth: usize,
1442 style: Style,
1443) {
1444 buffer.putc(line, offset + depth - 1, '|', style);
1445}
1446
1447fn num_overlap(
1448 a_start: usize,
1449 a_end: usize,
1450 b_start: usize,
1451 b_end: usize,
1452 inclusive: bool,
1453) -> bool {
1454 let extra = usize::from(inclusive);
1455 (b_start..b_end + extra).contains(&a_start) || (a_start..a_end + extra).contains(&b_start)
1456}
1457
1458fn overlaps(a1: &Annotation, a2: &Annotation, padding: usize) -> bool {
1459 num_overlap(
1460 a1.start_col,
1461 a1.end_col + padding,
1462 a2.start_col,
1463 a2.end_col,
1464 false,
1465 )
1466}
1467
1468fn emit_to_destination(
1469 rendered_buffer: &[Vec<StyledString>],
1470 lvl: Level,
1471 dst: &mut Destination,
1472 short_message: bool,
1473) -> io::Result<()> {
1474 use super::lock;
1475
1476 let mut dst = dst.writable();
1477
1478 let _buffer_lock = lock::acquire_global_lock("rustc_errors");
1493 for (pos, line) in rendered_buffer.iter().enumerate() {
1494 for part in line {
1495 #[cfg(feature = "tty-emitter")]
1496 dst.apply_style(lvl, part.style)?;
1497 write!(dst, "{}", part.text)?;
1498 dst.reset()?;
1499 }
1500 if !short_message && (!lvl.is_failure_note() || pos != rendered_buffer.len() - 1) {
1501 writeln!(dst)?;
1502 }
1503 }
1504 dst.flush()?;
1505 Ok(())
1506}
1507
1508pub enum Destination {
1509 #[cfg(feature = "tty-emitter")]
1510 Terminal(StandardStream),
1511 #[cfg(feature = "tty-emitter")]
1512 Buffered(BufferWriter),
1513 Raw(Box<dyn Write + Send>),
1514}
1515
1516pub enum WritableDst<'a> {
1517 #[cfg(feature = "tty-emitter")]
1518 Terminal(&'a mut StandardStream),
1519 #[cfg(feature = "tty-emitter")]
1520 Buffered(&'a mut BufferWriter, Buffer),
1521 Raw(&'a mut (dyn Write + Send)),
1522}
1523
1524impl Destination {
1525 #[cfg(feature = "tty-emitter")]
1526 fn from_stderr(color: ColorConfig) -> Destination {
1527 let choice = color.to_color_choice();
1528 if cfg!(windows) {
1535 Terminal(StandardStream::stderr(choice))
1536 } else {
1537 Buffered(BufferWriter::stderr(choice))
1538 }
1539 }
1540
1541 fn writable(&mut self) -> WritableDst<'_> {
1542 match *self {
1543 #[cfg(feature = "tty-emitter")]
1544 Destination::Terminal(ref mut t) => WritableDst::Terminal(t),
1545 #[cfg(feature = "tty-emitter")]
1546 Destination::Buffered(ref mut t) => {
1547 let buf = t.buffer();
1548 WritableDst::Buffered(t, buf)
1549 }
1550 Destination::Raw(ref mut t) => WritableDst::Raw(t),
1551 }
1552 }
1553}
1554
1555impl WritableDst<'_> {
1556 #[cfg(feature = "tty-emitter")]
1557 fn apply_style(&mut self, lvl: Level, style: Style) -> io::Result<()> {
1558 let mut spec = ColorSpec::new();
1559 match style {
1560 Style::LineAndColumn => {}
1561 Style::LineNumber => {
1562 spec.set_bold(true);
1563 spec.set_intense(true);
1564 if cfg!(windows) {
1565 spec.set_fg(Some(Color::Cyan));
1566 } else {
1567 spec.set_fg(Some(Color::Blue));
1568 }
1569 }
1570 Style::Quotation => {}
1571 Style::OldSchoolNoteText | Style::MainHeaderMsg => {
1572 spec.set_bold(true);
1573 if cfg!(windows) {
1574 spec.set_intense(true).set_fg(Some(Color::White));
1575 }
1576 }
1577 Style::UnderlinePrimary | Style::LabelPrimary => {
1578 spec = lvl.color();
1579 spec.set_bold(true);
1580 }
1581 Style::UnderlineSecondary | Style::LabelSecondary => {
1582 spec.set_bold(true).set_intense(true);
1583 if cfg!(windows) {
1584 spec.set_fg(Some(Color::Cyan));
1585 } else {
1586 spec.set_fg(Some(Color::Blue));
1587 }
1588 }
1589 Style::HeaderMsg | Style::NoStyle => {}
1590 Style::Level(lvl) => {
1591 spec = lvl.color();
1592 spec.set_bold(true);
1593 }
1594 Style::Highlight => {
1595 spec.set_bold(true);
1596 }
1597 }
1598 self.set_color(&spec)
1599 }
1600
1601 #[cfg(feature = "tty-emitter")]
1602 fn set_color(&mut self, color: &ColorSpec) -> io::Result<()> {
1603 match *self {
1604 #[cfg(feature = "tty-emitter")]
1605 WritableDst::Terminal(ref mut t) => t.set_color(color),
1606 #[cfg(feature = "tty-emitter")]
1607 WritableDst::Buffered(_, ref mut t) => t.set_color(color),
1608 WritableDst::Raw(_) => Ok(()),
1609 }
1610 }
1611
1612 fn reset(&mut self) -> io::Result<()> {
1613 match *self {
1614 #[cfg(feature = "tty-emitter")]
1615 WritableDst::Terminal(ref mut t) => t.reset(),
1616 #[cfg(feature = "tty-emitter")]
1617 WritableDst::Buffered(_, ref mut t) => t.reset(),
1618 WritableDst::Raw(_) => Ok(()),
1619 }
1620 }
1621}
1622
1623impl Write for WritableDst<'_> {
1624 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
1625 match *self {
1626 #[cfg(feature = "tty-emitter")]
1627 WritableDst::Terminal(ref mut t) => t.write(bytes),
1628 #[cfg(feature = "tty-emitter")]
1629 WritableDst::Buffered(_, ref mut buf) => buf.write(bytes),
1630 WritableDst::Raw(ref mut w) => w.write(bytes),
1631 }
1632 }
1633
1634 fn flush(&mut self) -> io::Result<()> {
1635 match *self {
1636 #[cfg(feature = "tty-emitter")]
1637 WritableDst::Terminal(ref mut t) => t.flush(),
1638 #[cfg(feature = "tty-emitter")]
1639 WritableDst::Buffered(_, ref mut buf) => buf.flush(),
1640 WritableDst::Raw(ref mut w) => w.flush(),
1641 }
1642 }
1643}
1644
1645impl Drop for WritableDst<'_> {
1646 fn drop(&mut self) {
1647 #[cfg(feature = "tty-emitter")]
1648 if let WritableDst::Buffered(ref mut dst, ref mut buf) = self {
1649 drop(dst.print(buf));
1650 }
1651 }
1652}