1use std::borrow::Cow;
11use std::cmp::{Reverse, max, min};
12use std::error::Report;
13use std::io::prelude::*;
14use std::io::{self, IsTerminal};
15use std::iter;
16use std::path::Path;
17use std::sync::Arc;
18
19use anstream::{AutoStream, ColorChoice};
20use anstyle::{AnsiColor, Effects};
21use derive_setters::Setters;
22use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
23use rustc_data_structures::sync::{DynSend, IntoDynSyncSend};
24use rustc_error_messages::{FluentArgs, SpanLabel};
25use rustc_lexer;
26use rustc_lint_defs::pluralize;
27use rustc_span::hygiene::{ExpnKind, MacroKind};
28use rustc_span::source_map::SourceMap;
29use rustc_span::{FileLines, FileName, SourceFile, Span, char_width, str_width};
30use tracing::{debug, instrument, trace, warn};
31
32use crate::registry::Registry;
33use crate::snippet::{
34 Annotation, AnnotationColumn, AnnotationType, Line, MultilineAnnotation, Style, StyledString,
35};
36use crate::styled_buffer::StyledBuffer;
37use crate::timings::TimingRecord;
38use crate::translation::{Translator, to_fluent_args};
39use crate::{
40 CodeSuggestion, DiagInner, DiagMessage, ErrCode, Level, MultiSpan, Subdiag,
41 SubstitutionHighlight, SuggestionStyle, TerminalUrl,
42};
43
44const DEFAULT_COLUMN_WIDTH: usize = 140;
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub enum HumanReadableErrorType {
50 Default { short: bool },
51 AnnotateSnippet { short: bool, unicode: bool },
52}
53
54impl HumanReadableErrorType {
55 pub fn short(&self) -> bool {
56 match self {
57 HumanReadableErrorType::Default { short }
58 | HumanReadableErrorType::AnnotateSnippet { short, .. } => *short,
59 }
60 }
61}
62
63#[derive(Clone, Copy, Debug)]
64struct Margin {
65 pub whitespace_left: usize,
67 pub span_left: usize,
69 pub span_right: usize,
71 pub computed_left: usize,
73 pub computed_right: usize,
75 pub column_width: usize,
78 pub label_right: usize,
81}
82
83impl Margin {
84 fn new(
85 whitespace_left: usize,
86 span_left: usize,
87 span_right: usize,
88 label_right: usize,
89 column_width: usize,
90 max_line_len: usize,
91 ) -> Self {
92 let mut m = Margin {
102 whitespace_left: whitespace_left.saturating_sub(6),
103 span_left: span_left.saturating_sub(6),
104 span_right: span_right + 6,
105 computed_left: 0,
106 computed_right: 0,
107 column_width,
108 label_right: label_right + 6,
109 };
110 m.compute(max_line_len);
111 m
112 }
113
114 fn was_cut_left(&self) -> bool {
115 self.computed_left > 0
116 }
117
118 fn compute(&mut self, max_line_len: usize) {
119 self.computed_left = if self.whitespace_left > 20 {
124 self.whitespace_left - 16 } else {
126 0
127 };
128 self.computed_right = max(max_line_len, self.computed_left);
131
132 if self.computed_right - self.computed_left > self.column_width {
133 if self.label_right - self.whitespace_left <= self.column_width {
135 self.computed_left = self.whitespace_left;
137 self.computed_right = self.computed_left + self.column_width;
138 } else if self.label_right - self.span_left <= self.column_width {
139 let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2;
141 self.computed_left = self.span_left.saturating_sub(padding_left);
142 self.computed_right = self.computed_left + self.column_width;
143 } else if self.span_right - self.span_left <= self.column_width {
144 let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2;
146 self.computed_left = self.span_left.saturating_sub(padding_left);
147 self.computed_right = self.computed_left + self.column_width;
148 } else {
149 self.computed_left = self.span_left;
151 self.computed_right = self.span_right;
152 }
153 }
154 }
155
156 fn left(&self, line_len: usize) -> usize {
157 min(self.computed_left, line_len)
158 }
159
160 fn right(&self, line_len: usize) -> usize {
161 if line_len.saturating_sub(self.computed_left) <= self.column_width {
162 line_len
163 } else {
164 min(line_len, self.computed_right)
165 }
166 }
167}
168
169pub enum TimingEvent {
170 Start,
171 End,
172}
173
174const ANONYMIZED_LINE_NUM: &str = "LL";
175
176pub type DynEmitter = dyn Emitter + DynSend;
177
178pub trait Emitter {
180 fn emit_diagnostic(&mut self, diag: DiagInner, registry: &Registry);
182
183 fn emit_artifact_notification(&mut self, _path: &Path, _artifact_type: &str) {}
186
187 fn emit_timing_section(&mut self, _record: TimingRecord, _event: TimingEvent) {}
190
191 fn emit_future_breakage_report(&mut self, _diags: Vec<DiagInner>, _registry: &Registry) {}
194
195 fn emit_unused_externs(
198 &mut self,
199 _lint_level: rustc_lint_defs::Level,
200 _unused_externs: &[&str],
201 ) {
202 }
203
204 fn should_show_explain(&self) -> bool {
206 true
207 }
208
209 fn supports_color(&self) -> bool {
211 false
212 }
213
214 fn source_map(&self) -> Option<&SourceMap>;
215
216 fn translator(&self) -> &Translator;
217
218 fn primary_span_formatted(
230 &self,
231 primary_span: &mut MultiSpan,
232 suggestions: &mut Vec<CodeSuggestion>,
233 fluent_args: &FluentArgs<'_>,
234 ) {
235 if let Some((sugg, rest)) = suggestions.split_first() {
236 let msg = self
237 .translator()
238 .translate_message(&sugg.msg, fluent_args)
239 .map_err(Report::new)
240 .unwrap();
241 if rest.is_empty()
242 && let [substitution] = sugg.substitutions.as_slice()
245 && let [part] = substitution.parts.as_slice()
247 && msg.split_whitespace().count() < 10
249 && !part.snippet.contains('\n')
251 && ![
252 SuggestionStyle::HideCodeAlways,
254 SuggestionStyle::CompletelyHidden,
256 SuggestionStyle::ShowAlways,
258 ].contains(&sugg.style)
259 {
260 let snippet = part.snippet.trim();
261 let msg = if snippet.is_empty() || sugg.style.hide_inline() {
262 format!("help: {msg}")
265 } else {
266 let confusion_type = self
268 .source_map()
269 .map(|sm| detect_confusion_type(sm, snippet, part.span))
270 .unwrap_or(ConfusionType::None);
271 format!("help: {}{}: `{}`", msg, confusion_type.label_text(), snippet,)
272 };
273 primary_span.push_span_label(part.span, msg);
274
275 suggestions.clear();
277 } else {
278 }
283 } else {
284 }
286 }
287
288 fn fix_multispans_in_extern_macros_and_render_macro_backtrace(
289 &self,
290 span: &mut MultiSpan,
291 children: &mut Vec<Subdiag>,
292 level: &Level,
293 backtrace: bool,
294 ) {
295 let has_macro_spans: Vec<_> = iter::once(&*span)
298 .chain(children.iter().map(|child| &child.span))
299 .flat_map(|span| span.primary_spans())
300 .flat_map(|sp| sp.macro_backtrace())
301 .filter_map(|expn_data| {
302 match expn_data.kind {
303 ExpnKind::Root => None,
304
305 ExpnKind::Desugaring(..) | ExpnKind::AstPass(..) => None,
308
309 ExpnKind::Macro(macro_kind, name) => {
310 Some((macro_kind, name, expn_data.hide_backtrace))
311 }
312 }
313 })
314 .collect();
315
316 if !backtrace {
317 self.fix_multispans_in_extern_macros(span, children);
318 }
319
320 self.render_multispans_macro_backtrace(span, children, backtrace);
321
322 if !backtrace {
323 if let Some((macro_kind, name, _)) = has_macro_spans.first()
326 && let Some((_, _, false)) = has_macro_spans.last()
327 {
328 let and_then = if let Some((macro_kind, last_name, _)) = has_macro_spans.last()
330 && last_name != name
331 {
332 let descr = macro_kind.descr();
333 format!(" which comes from the expansion of the {descr} `{last_name}`")
334 } else {
335 "".to_string()
336 };
337
338 let descr = macro_kind.descr();
339 let msg = format!(
340 "this {level} originates in the {descr} `{name}`{and_then} \
341 (in Nightly builds, run with -Z macro-backtrace for more info)",
342 );
343
344 children.push(Subdiag {
345 level: Level::Note,
346 messages: vec![(DiagMessage::from(msg), Style::NoStyle)],
347 span: MultiSpan::new(),
348 });
349 }
350 }
351 }
352
353 fn render_multispans_macro_backtrace(
354 &self,
355 span: &mut MultiSpan,
356 children: &mut Vec<Subdiag>,
357 backtrace: bool,
358 ) {
359 for span in iter::once(span).chain(children.iter_mut().map(|child| &mut child.span)) {
360 self.render_multispan_macro_backtrace(span, backtrace);
361 }
362 }
363
364 fn render_multispan_macro_backtrace(&self, span: &mut MultiSpan, always_backtrace: bool) {
365 let mut new_labels = FxIndexSet::default();
366
367 for &sp in span.primary_spans() {
368 if sp.is_dummy() {
369 continue;
370 }
371
372 let macro_backtrace: Vec<_> = sp.macro_backtrace().collect();
376 for (i, trace) in macro_backtrace.iter().rev().enumerate() {
377 if trace.def_site.is_dummy() {
378 continue;
379 }
380
381 if always_backtrace {
382 new_labels.insert((
383 trace.def_site,
384 format!(
385 "in this expansion of `{}`{}",
386 trace.kind.descr(),
387 if macro_backtrace.len() > 1 {
388 format!(" (#{})", i + 1)
391 } else {
392 String::new()
393 },
394 ),
395 ));
396 }
397
398 let redundant_span = trace.call_site.contains(sp);
410
411 if !redundant_span || always_backtrace {
412 let msg: Cow<'static, _> = match trace.kind {
413 ExpnKind::Macro(MacroKind::Attr, _) => {
414 "this attribute macro expansion".into()
415 }
416 ExpnKind::Macro(MacroKind::Derive, _) => {
417 "this derive macro expansion".into()
418 }
419 ExpnKind::Macro(MacroKind::Bang, _) => "this macro invocation".into(),
420 ExpnKind::Root => "the crate root".into(),
421 ExpnKind::AstPass(kind) => kind.descr().into(),
422 ExpnKind::Desugaring(kind) => {
423 format!("this {} desugaring", kind.descr()).into()
424 }
425 };
426 new_labels.insert((
427 trace.call_site,
428 format!(
429 "in {}{}",
430 msg,
431 if macro_backtrace.len() > 1 && always_backtrace {
432 format!(" (#{})", i + 1)
435 } else {
436 String::new()
437 },
438 ),
439 ));
440 }
441 if !always_backtrace {
442 break;
443 }
444 }
445 }
446
447 for (label_span, label_text) in new_labels {
448 span.push_span_label(label_span, label_text);
449 }
450 }
451
452 fn fix_multispans_in_extern_macros(&self, span: &mut MultiSpan, children: &mut Vec<Subdiag>) {
456 debug!("fix_multispans_in_extern_macros: before: span={:?} children={:?}", span, children);
457 self.fix_multispan_in_extern_macros(span);
458 for child in children.iter_mut() {
459 self.fix_multispan_in_extern_macros(&mut child.span);
460 }
461 debug!("fix_multispans_in_extern_macros: after: span={:?} children={:?}", span, children);
462 }
463
464 fn fix_multispan_in_extern_macros(&self, span: &mut MultiSpan) {
468 let Some(source_map) = self.source_map() else { return };
469 let replacements: Vec<(Span, Span)> = span
471 .primary_spans()
472 .iter()
473 .copied()
474 .chain(span.span_labels().iter().map(|sp_label| sp_label.span))
475 .filter_map(|sp| {
476 if !sp.is_dummy() && source_map.is_imported(sp) {
477 let maybe_callsite = sp.source_callsite();
478 if sp != maybe_callsite {
479 return Some((sp, maybe_callsite));
480 }
481 }
482 None
483 })
484 .collect();
485
486 for (from, to) in replacements {
488 span.replace(from, to);
489 }
490 }
491}
492
493impl Emitter for HumanEmitter {
494 fn source_map(&self) -> Option<&SourceMap> {
495 self.sm.as_deref()
496 }
497
498 fn emit_diagnostic(&mut self, mut diag: DiagInner, _registry: &Registry) {
499 let fluent_args = to_fluent_args(diag.args.iter());
500
501 if self.track_diagnostics && diag.span.has_primary_spans() && !diag.span.is_dummy() {
502 diag.children.insert(0, diag.emitted_at_sub_diag());
503 }
504
505 let mut suggestions = diag.suggestions.unwrap_tag();
506 self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args);
507
508 self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
509 &mut diag.span,
510 &mut diag.children,
511 &diag.level,
512 self.macro_backtrace,
513 );
514
515 self.emit_messages_default(
516 &diag.level,
517 &diag.messages,
518 &fluent_args,
519 &diag.code,
520 &diag.span,
521 &diag.children,
522 &suggestions,
523 );
524 }
525
526 fn should_show_explain(&self) -> bool {
527 !self.short_message
528 }
529
530 fn translator(&self) -> &Translator {
531 &self.translator
532 }
533}
534
535pub struct EmitterWithNote {
537 pub emitter: Box<dyn Emitter + DynSend>,
538 pub note: String,
539}
540
541impl Emitter for EmitterWithNote {
542 fn source_map(&self) -> Option<&SourceMap> {
543 None
544 }
545
546 fn emit_diagnostic(&mut self, mut diag: DiagInner, registry: &Registry) {
547 diag.sub(Level::Note, self.note.clone(), MultiSpan::new());
548 self.emitter.emit_diagnostic(diag, registry);
549 }
550
551 fn translator(&self) -> &Translator {
552 self.emitter.translator()
553 }
554}
555
556pub struct SilentEmitter {
557 pub translator: Translator,
558}
559
560impl Emitter for SilentEmitter {
561 fn source_map(&self) -> Option<&SourceMap> {
562 None
563 }
564
565 fn emit_diagnostic(&mut self, _diag: DiagInner, _registry: &Registry) {}
566
567 fn translator(&self) -> &Translator {
568 &self.translator
569 }
570}
571
572pub const MAX_SUGGESTIONS: usize = 4;
576
577#[derive(Clone, Copy, Debug, PartialEq, Eq)]
578pub enum ColorConfig {
579 Auto,
580 Always,
581 Never,
582}
583
584impl ColorConfig {
585 pub fn to_color_choice(self) -> ColorChoice {
586 match self {
587 ColorConfig::Always => {
588 if io::stderr().is_terminal() {
589 ColorChoice::Always
590 } else {
591 ColorChoice::AlwaysAnsi
592 }
593 }
594 ColorConfig::Never => ColorChoice::Never,
595 ColorConfig::Auto if io::stderr().is_terminal() => ColorChoice::Auto,
596 ColorConfig::Auto => ColorChoice::Never,
597 }
598 }
599}
600
601#[derive(Debug, Clone, Copy, PartialEq, Eq)]
602pub enum OutputTheme {
603 Ascii,
604 Unicode,
605}
606
607#[derive(Setters)]
609pub struct HumanEmitter {
610 #[setters(skip)]
611 dst: IntoDynSyncSend<Destination>,
612 sm: Option<Arc<SourceMap>>,
613 #[setters(skip)]
614 translator: Translator,
615 short_message: bool,
616 ui_testing: bool,
617 ignored_directories_in_source_blocks: Vec<String>,
618 diagnostic_width: Option<usize>,
619
620 macro_backtrace: bool,
621 track_diagnostics: bool,
622 terminal_url: TerminalUrl,
623 theme: OutputTheme,
624}
625
626#[derive(Debug)]
627pub(crate) struct FileWithAnnotatedLines {
628 pub(crate) file: Arc<SourceFile>,
629 pub(crate) lines: Vec<Line>,
630 multiline_depth: usize,
631}
632
633impl HumanEmitter {
634 pub fn new(dst: Destination, translator: Translator) -> HumanEmitter {
635 HumanEmitter {
636 dst: IntoDynSyncSend(dst),
637 sm: None,
638 translator,
639 short_message: false,
640 ui_testing: false,
641 ignored_directories_in_source_blocks: Vec::new(),
642 diagnostic_width: None,
643 macro_backtrace: false,
644 track_diagnostics: false,
645 terminal_url: TerminalUrl::No,
646 theme: OutputTheme::Ascii,
647 }
648 }
649
650 fn maybe_anonymized(&self, line_num: usize) -> Cow<'static, str> {
651 if self.ui_testing {
652 Cow::Borrowed(ANONYMIZED_LINE_NUM)
653 } else {
654 Cow::Owned(line_num.to_string())
655 }
656 }
657
658 fn draw_line(
659 &self,
660 buffer: &mut StyledBuffer,
661 source_string: &str,
662 line_index: usize,
663 line_offset: usize,
664 width_offset: usize,
665 code_offset: usize,
666 margin: Margin,
667 ) -> usize {
668 let line_len = source_string.len();
669 let left = margin.left(line_len);
671 let right = margin.right(line_len);
672 let code: String = source_string
675 .chars()
676 .enumerate()
677 .skip_while(|(i, _)| *i < left)
678 .take_while(|(i, _)| *i < right)
679 .map(|(_, c)| c)
680 .collect();
681 let code = normalize_whitespace(&code);
682 let was_cut_right =
683 source_string.chars().enumerate().skip_while(|(i, _)| *i < right).next().is_some();
684 buffer.puts(line_offset, code_offset, &code, Style::Quotation);
685 let placeholder = self.margin();
686 if margin.was_cut_left() {
687 buffer.puts(line_offset, code_offset, placeholder, Style::LineNumber);
689 }
690 if was_cut_right {
691 let padding = str_width(placeholder);
692 buffer.puts(
694 line_offset,
695 code_offset + str_width(&code) - padding,
696 placeholder,
697 Style::LineNumber,
698 );
699 }
700 self.draw_line_num(buffer, line_index, line_offset, width_offset - 3);
701 self.draw_col_separator_no_space(buffer, line_offset, width_offset - 2);
702 left
703 }
704
705 #[instrument(level = "trace", skip(self), ret)]
706 fn render_source_line(
707 &self,
708 buffer: &mut StyledBuffer,
709 file: Arc<SourceFile>,
710 line: &Line,
711 width_offset: usize,
712 code_offset: usize,
713 margin: Margin,
714 close_window: bool,
715 ) -> Vec<(usize, Style)> {
716 if line.line_index == 0 {
731 return Vec::new();
732 }
733
734 let Some(source_string) = file.get_line(line.line_index - 1) else {
735 return Vec::new();
736 };
737 trace!(?source_string);
738
739 let line_offset = buffer.num_lines();
740
741 let left = self.draw_line(
744 buffer,
745 &source_string,
746 line.line_index,
747 line_offset,
748 width_offset,
749 code_offset,
750 margin,
751 );
752
753 let mut buffer_ops = vec![];
770 let mut annotations = vec![];
771 let mut short_start = true;
772 for ann in &line.annotations {
773 if let AnnotationType::MultilineStart(depth) = ann.annotation_type {
774 if source_string.chars().take(ann.start_col.file).all(|c| c.is_whitespace()) {
775 let uline = self.underline(ann.is_primary);
776 let chr = uline.multiline_whole_line;
777 annotations.push((depth, uline.style));
778 buffer_ops.push((line_offset, width_offset + depth - 1, chr, uline.style));
779 } else {
780 short_start = false;
781 break;
782 }
783 } else if let AnnotationType::MultilineLine(_) = ann.annotation_type {
784 } else {
785 short_start = false;
786 break;
787 }
788 }
789 if short_start {
790 for (y, x, c, s) in buffer_ops {
791 buffer.putc(y, x, c, s);
792 }
793 return annotations;
794 }
795
796 let mut annotations = line.annotations.clone();
829 annotations.sort_by_key(|a| Reverse(a.start_col));
830
831 let mut overlap = vec![false; annotations.len()];
894 let mut annotations_position = vec![];
895 let mut line_len: usize = 0;
896 let mut p = 0;
897 for (i, annotation) in annotations.iter().enumerate() {
898 for (j, next) in annotations.iter().enumerate() {
899 if overlaps(next, annotation, 0) && j > i {
900 overlap[i] = true;
901 overlap[j] = true;
902 }
903 if overlaps(next, annotation, 0) && annotation.has_label() && j > i && p == 0
907 {
909 if next.start_col == annotation.start_col
912 && next.end_col == annotation.end_col
913 && !next.has_label()
914 {
915 continue;
916 }
917
918 p += 1;
920 break;
921 }
922 }
923 annotations_position.push((p, annotation));
924 for (j, next) in annotations.iter().enumerate() {
925 if j > i {
926 let l = next.label.as_ref().map_or(0, |label| label.len() + 2);
927 if (overlaps(next, annotation, l) && annotation.has_label() && next.has_label()) || (annotation.takes_space() && next.has_label()) || (annotation.has_label() && next.takes_space())
944 || (annotation.takes_space() && next.takes_space())
945 || (overlaps(next, annotation, l)
946 && next.end_col <= annotation.end_col
947 && next.has_label()
948 && p == 0)
949 {
951 p += 1;
953 break;
954 }
955 }
956 }
957 line_len = max(line_len, p);
958 }
959
960 if line_len != 0 {
961 line_len += 1;
962 }
963
964 if line.annotations.iter().all(|a| a.is_line()) {
967 return vec![];
968 }
969
970 if annotations_position
971 .iter()
972 .all(|(_, ann)| matches!(ann.annotation_type, AnnotationType::MultilineStart(_)))
973 && let Some(max_pos) = annotations_position.iter().map(|(pos, _)| *pos).max()
974 {
975 for (pos, _) in &mut annotations_position {
988 *pos = max_pos - *pos;
989 }
990 line_len = line_len.saturating_sub(1);
993 }
994
995 for pos in 0..=line_len {
1007 self.draw_col_separator_no_space(buffer, line_offset + pos + 1, width_offset - 2);
1008 }
1009 if close_window {
1010 self.draw_col_separator_end(buffer, line_offset + line_len + 1, width_offset - 2);
1011 }
1012
1013 for &(pos, annotation) in &annotations_position {
1026 let underline = self.underline(annotation.is_primary);
1027 let pos = pos + 1;
1028 match annotation.annotation_type {
1029 AnnotationType::MultilineStart(depth) | AnnotationType::MultilineEnd(depth) => {
1030 let pre: usize = source_string
1031 .chars()
1032 .take(annotation.start_col.file)
1033 .skip(left)
1034 .map(|c| char_width(c))
1035 .sum();
1036 self.draw_range(
1037 buffer,
1038 underline.multiline_horizontal,
1039 line_offset + pos,
1040 width_offset + depth,
1041 code_offset + pre,
1042 underline.style,
1043 );
1044 }
1045 _ => {}
1046 }
1047 }
1048
1049 for &(pos, annotation) in &annotations_position {
1061 let underline = self.underline(annotation.is_primary);
1062 let pos = pos + 1;
1063
1064 let code_offset = code_offset
1065 + source_string
1066 .chars()
1067 .take(annotation.start_col.file)
1068 .skip(left)
1069 .map(|c| char_width(c))
1070 .sum::<usize>();
1071 if pos > 1 && (annotation.has_label() || annotation.takes_space()) {
1072 for p in line_offset + 1..=line_offset + pos {
1073 buffer.putc(
1074 p,
1075 code_offset,
1076 match annotation.annotation_type {
1077 AnnotationType::MultilineLine(_) => underline.multiline_vertical,
1078 _ => underline.vertical_text_line,
1079 },
1080 underline.style,
1081 );
1082 }
1083 if let AnnotationType::MultilineStart(_) = annotation.annotation_type {
1084 buffer.putc(
1085 line_offset + pos,
1086 code_offset,
1087 underline.bottom_right,
1088 underline.style,
1089 );
1090 }
1091 if let AnnotationType::MultilineEnd(_) = annotation.annotation_type
1092 && annotation.has_label()
1093 {
1094 buffer.putc(
1095 line_offset + pos,
1096 code_offset,
1097 underline.multiline_bottom_right_with_text,
1098 underline.style,
1099 );
1100 }
1101 }
1102 match annotation.annotation_type {
1103 AnnotationType::MultilineStart(depth) => {
1104 buffer.putc(
1105 line_offset + pos,
1106 width_offset + depth - 1,
1107 underline.top_left,
1108 underline.style,
1109 );
1110 for p in line_offset + pos + 1..line_offset + line_len + 2 {
1111 buffer.putc(
1112 p,
1113 width_offset + depth - 1,
1114 underline.multiline_vertical,
1115 underline.style,
1116 );
1117 }
1118 }
1119 AnnotationType::MultilineEnd(depth) => {
1120 for p in line_offset..line_offset + pos {
1121 buffer.putc(
1122 p,
1123 width_offset + depth - 1,
1124 underline.multiline_vertical,
1125 underline.style,
1126 );
1127 }
1128 buffer.putc(
1129 line_offset + pos,
1130 width_offset + depth - 1,
1131 underline.bottom_left,
1132 underline.style,
1133 );
1134 }
1135 _ => (),
1136 }
1137 }
1138
1139 for &(pos, annotation) in &annotations_position {
1151 let style =
1152 if annotation.is_primary { Style::LabelPrimary } else { Style::LabelSecondary };
1153 let (pos, col) = if pos == 0 {
1154 let pre: usize = source_string
1155 .chars()
1156 .take(annotation.end_col.file)
1157 .skip(left)
1158 .map(|c| char_width(c))
1159 .sum();
1160 if annotation.end_col.file == 0 {
1161 (pos + 1, (pre + 2))
1162 } else {
1163 let pad = if annotation.end_col.file - annotation.start_col.file == 0 {
1164 2
1165 } else {
1166 1
1167 };
1168 (pos + 1, (pre + pad))
1169 }
1170 } else {
1171 let pre: usize = source_string
1172 .chars()
1173 .take(annotation.start_col.file)
1174 .skip(left)
1175 .map(|c| char_width(c))
1176 .sum();
1177 (pos + 2, pre)
1178 };
1179 if let Some(ref label) = annotation.label {
1180 buffer.puts(line_offset + pos, code_offset + col, label, style);
1181 }
1182 }
1183
1184 annotations_position.sort_by_key(|(_, ann)| {
1193 (Reverse(ann.len()), ann.is_primary)
1195 });
1196
1197 for &(pos, annotation) in &annotations_position {
1209 let uline = self.underline(annotation.is_primary);
1210 let width = annotation.end_col.file - annotation.start_col.file;
1211 let previous: String =
1212 source_string.chars().take(annotation.start_col.file).skip(left).collect();
1213 let underlined: String =
1214 source_string.chars().skip(annotation.start_col.file).take(width).collect();
1215 debug!(?previous, ?underlined);
1216 let code_offset = code_offset
1217 + source_string
1218 .chars()
1219 .take(annotation.start_col.file)
1220 .skip(left)
1221 .map(|c| char_width(c))
1222 .sum::<usize>();
1223 let ann_width: usize = source_string
1224 .chars()
1225 .skip(annotation.start_col.file)
1226 .take(width)
1227 .map(|c| char_width(c))
1228 .sum();
1229 let ann_width = if ann_width == 0
1230 && matches!(annotation.annotation_type, AnnotationType::Singleline)
1231 {
1232 1
1233 } else {
1234 ann_width
1235 };
1236 for p in 0..ann_width {
1237 buffer.putc(line_offset + 1, code_offset + p, uline.underline, uline.style);
1239 }
1240
1241 if pos == 0
1242 && matches!(
1243 annotation.annotation_type,
1244 AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_)
1245 )
1246 {
1247 buffer.putc(
1249 line_offset + 1,
1250 code_offset,
1251 match annotation.annotation_type {
1252 AnnotationType::MultilineStart(_) => uline.top_right_flat,
1253 AnnotationType::MultilineEnd(_) => uline.multiline_end_same_line,
1254 _ => panic!("unexpected annotation type: {annotation:?}"),
1255 },
1256 uline.style,
1257 );
1258 } else if pos != 0
1259 && matches!(
1260 annotation.annotation_type,
1261 AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_)
1262 )
1263 {
1264 buffer.putc(
1267 line_offset + 1,
1268 code_offset,
1269 match annotation.annotation_type {
1270 AnnotationType::MultilineStart(_) => uline.multiline_start_down,
1271 AnnotationType::MultilineEnd(_) => uline.multiline_end_up,
1272 _ => panic!("unexpected annotation type: {annotation:?}"),
1273 },
1274 uline.style,
1275 );
1276 } else if pos != 0 && annotation.has_label() {
1277 buffer.putc(line_offset + 1, code_offset, uline.label_start, uline.style);
1279 }
1280 }
1281
1282 for (i, (_pos, annotation)) in annotations_position.iter().enumerate() {
1286 if overlap[i] {
1288 continue;
1289 };
1290 let AnnotationType::Singleline = annotation.annotation_type else { continue };
1291 let width = annotation.end_col.display - annotation.start_col.display;
1292 if width > margin.column_width * 2 && width > 10 {
1293 let pad = max(margin.column_width / 3, 5);
1296 buffer.replace(
1298 line_offset,
1299 annotation.start_col.file + pad,
1300 annotation.end_col.file - pad,
1301 self.margin(),
1302 );
1303 buffer.replace(
1305 line_offset + 1,
1306 annotation.start_col.file + pad,
1307 annotation.end_col.file - pad,
1308 self.margin(),
1309 );
1310 }
1311 }
1312 annotations_position
1313 .iter()
1314 .filter_map(|&(_, annotation)| match annotation.annotation_type {
1315 AnnotationType::MultilineStart(p) | AnnotationType::MultilineEnd(p) => {
1316 let style = if annotation.is_primary {
1317 Style::LabelPrimary
1318 } else {
1319 Style::LabelSecondary
1320 };
1321 Some((p, style))
1322 }
1323 _ => None,
1324 })
1325 .collect::<Vec<_>>()
1326 }
1327
1328 fn get_multispan_max_line_num(&mut self, msp: &MultiSpan) -> usize {
1329 let Some(ref sm) = self.sm else {
1330 return 0;
1331 };
1332
1333 let will_be_emitted = |span: Span| {
1334 !span.is_dummy() && {
1335 let file = sm.lookup_source_file(span.hi());
1336 should_show_source_code(&self.ignored_directories_in_source_blocks, sm, &file)
1337 }
1338 };
1339
1340 let mut max = 0;
1341 for primary_span in msp.primary_spans() {
1342 if will_be_emitted(*primary_span) {
1343 let hi = sm.lookup_char_pos(primary_span.hi());
1344 max = (hi.line).max(max);
1345 }
1346 }
1347 if !self.short_message {
1348 for span_label in msp.span_labels() {
1349 if will_be_emitted(span_label.span) {
1350 let hi = sm.lookup_char_pos(span_label.span.hi());
1351 max = (hi.line).max(max);
1352 }
1353 }
1354 }
1355
1356 max
1357 }
1358
1359 fn get_max_line_num(&mut self, span: &MultiSpan, children: &[Subdiag]) -> usize {
1360 let primary = self.get_multispan_max_line_num(span);
1361 children
1362 .iter()
1363 .map(|sub| self.get_multispan_max_line_num(&sub.span))
1364 .max()
1365 .unwrap_or(0)
1366 .max(primary)
1367 }
1368
1369 fn msgs_to_buffer(
1372 &self,
1373 buffer: &mut StyledBuffer,
1374 msgs: &[(DiagMessage, Style)],
1375 args: &FluentArgs<'_>,
1376 padding: usize,
1377 label: &str,
1378 override_style: Option<Style>,
1379 ) -> usize {
1380 let padding = " ".repeat(padding + label.len() + 5);
1397
1398 fn style_or_override(style: Style, override_: Option<Style>) -> Style {
1400 match (style, override_) {
1401 (Style::NoStyle, Some(override_)) => override_,
1402 _ => style,
1403 }
1404 }
1405
1406 let mut line_number = 0;
1407
1408 for (text, style) in msgs.iter() {
1428 let text = self.translator.translate_message(text, args).map_err(Report::new).unwrap();
1429 let text = &normalize_whitespace(&text);
1430 let lines = text.split('\n').collect::<Vec<_>>();
1431 if lines.len() > 1 {
1432 for (i, line) in lines.iter().enumerate() {
1433 if i != 0 {
1434 line_number += 1;
1435 buffer.append(line_number, &padding, Style::NoStyle);
1436 }
1437 buffer.append(line_number, line, style_or_override(*style, override_style));
1438 }
1439 } else {
1440 buffer.append(line_number, text, style_or_override(*style, override_style));
1441 }
1442 }
1443 line_number
1444 }
1445
1446 #[instrument(level = "trace", skip(self, args), ret)]
1447 fn emit_messages_default_inner(
1448 &mut self,
1449 msp: &MultiSpan,
1450 msgs: &[(DiagMessage, Style)],
1451 args: &FluentArgs<'_>,
1452 code: &Option<ErrCode>,
1453 level: &Level,
1454 max_line_num_len: usize,
1455 is_secondary: bool,
1456 is_cont: bool,
1457 ) -> io::Result<CodeWindowStatus> {
1458 let mut buffer = StyledBuffer::new();
1459
1460 if !msp.has_primary_spans() && !msp.has_span_labels() && is_secondary && !self.short_message
1461 {
1462 for _ in 0..max_line_num_len {
1464 buffer.prepend(0, " ", Style::NoStyle);
1465 }
1466 self.draw_note_separator(&mut buffer, 0, max_line_num_len + 1, is_cont);
1467 if *level != Level::FailureNote {
1468 buffer.append(0, level.to_str(), Style::MainHeaderMsg);
1469 buffer.append(0, ": ", Style::NoStyle);
1470 }
1471 let printed_lines =
1472 self.msgs_to_buffer(&mut buffer, msgs, args, max_line_num_len, "note", None);
1473 if is_cont && matches!(self.theme, OutputTheme::Unicode) {
1474 for i in 1..=printed_lines {
1486 self.draw_col_separator_no_space(&mut buffer, i, max_line_num_len + 1);
1487 }
1488 }
1489 } else {
1490 let mut label_width = 0;
1491 if *level != Level::FailureNote {
1493 buffer.append(0, level.to_str(), Style::Level(*level));
1494 label_width += level.to_str().len();
1495 }
1496 if let Some(code) = code {
1497 buffer.append(0, "[", Style::Level(*level));
1498 let code = if let TerminalUrl::Yes = self.terminal_url {
1499 let path = "https://doc.rust-lang.org/error_codes";
1500 format!("\x1b]8;;{path}/{code}.html\x07{code}\x1b]8;;\x07")
1501 } else {
1502 code.to_string()
1503 };
1504 buffer.append(0, &code, Style::Level(*level));
1505 buffer.append(0, "]", Style::Level(*level));
1506 label_width += 2 + code.len();
1507 }
1508 let header_style = if is_secondary {
1509 Style::HeaderMsg
1510 } else if self.short_message {
1511 Style::NoStyle
1513 } else {
1514 Style::MainHeaderMsg
1515 };
1516 if *level != Level::FailureNote {
1517 buffer.append(0, ": ", header_style);
1518 label_width += 2;
1519 }
1520 let mut line = 0;
1521 let mut pad = false;
1522 for (text, style) in msgs.iter() {
1523 let text =
1524 self.translator.translate_message(text, args).map_err(Report::new).unwrap();
1525 for text in normalize_whitespace(&text).split('\n') {
1527 buffer.append(
1528 line,
1529 &format!(
1530 "{}{}",
1531 if pad { " ".repeat(label_width) } else { String::new() },
1532 text
1533 ),
1534 match style {
1535 Style::Highlight => *style,
1536 _ => header_style,
1537 },
1538 );
1539 line += 1;
1540 pad = true;
1541 }
1542 pad = false;
1543 if line > 0 {
1549 line -= 1;
1550 }
1551 }
1552 if self.short_message {
1553 let labels = msp
1554 .span_labels()
1555 .into_iter()
1556 .filter_map(|label| match label.label {
1557 Some(msg) if label.is_primary => {
1558 let text = self.translator.translate_message(&msg, args).ok()?;
1559 if !text.trim().is_empty() { Some(text.to_string()) } else { None }
1560 }
1561 _ => None,
1562 })
1563 .collect::<Vec<_>>()
1564 .join(", ");
1565 if !labels.is_empty() {
1566 buffer.append(line, ": ", Style::NoStyle);
1567 buffer.append(line, &labels, Style::NoStyle);
1568 }
1569 }
1570 }
1571 let mut annotated_files = FileWithAnnotatedLines::collect_annotations(self, args, msp);
1572 trace!("{annotated_files:#?}");
1573 let mut code_window_status = CodeWindowStatus::Open;
1574
1575 let primary_span = msp.primary_span().unwrap_or_default();
1577 let (Some(sm), false) = (self.sm.as_ref(), primary_span.is_dummy()) else {
1578 return emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)
1580 .map(|_| code_window_status);
1581 };
1582 let primary_lo = sm.lookup_char_pos(primary_span.lo());
1583 if let Ok(pos) =
1584 annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
1585 {
1586 annotated_files.swap(0, pos);
1587 }
1588
1589 let mut col_sep_before_no_show_source = false;
1592 let annotated_files_len = annotated_files.len();
1593 for (file_idx, annotated_file) in annotated_files.into_iter().enumerate() {
1595 if !should_show_source_code(
1597 &self.ignored_directories_in_source_blocks,
1598 sm,
1599 &annotated_file.file,
1600 ) {
1601 if !self.short_message {
1602 if col_sep_before_no_show_source {
1613 let buffer_msg_line_offset = buffer.num_lines();
1614 self.draw_col_separator_end(
1615 &mut buffer,
1616 buffer_msg_line_offset,
1617 max_line_num_len + 1,
1618 );
1619 }
1620 col_sep_before_no_show_source = false;
1621
1622 for (annotation_id, line) in annotated_file.lines.iter().enumerate() {
1624 let mut annotations = line.annotations.clone();
1625 annotations.sort_by_key(|a| Reverse(a.start_col));
1626 let mut line_idx = buffer.num_lines();
1627
1628 let labels: Vec<_> = annotations
1629 .iter()
1630 .filter_map(|a| Some((a.label.as_ref()?, a.is_primary)))
1631 .filter(|(l, _)| !l.is_empty())
1632 .collect();
1633
1634 if annotation_id == 0 || !labels.is_empty() {
1635 buffer.append(
1636 line_idx,
1637 &format!(
1638 "{}:{}:{}",
1639 sm.filename_for_diagnostics(&annotated_file.file.name),
1640 sm.doctest_offset_line(
1641 &annotated_file.file.name,
1642 line.line_index
1643 ),
1644 annotations[0].start_col.file + 1,
1645 ),
1646 Style::LineAndColumn,
1647 );
1648 if annotation_id == 0 {
1649 buffer.prepend(line_idx, self.file_start(), Style::LineNumber);
1650 } else {
1651 buffer.prepend(
1652 line_idx,
1653 self.secondary_file_start(),
1654 Style::LineNumber,
1655 );
1656 }
1657 for _ in 0..max_line_num_len {
1658 buffer.prepend(line_idx, " ", Style::NoStyle);
1659 }
1660 line_idx += 1;
1661 }
1662 if is_cont
1663 && file_idx == annotated_files_len - 1
1664 && annotation_id == annotated_file.lines.len() - 1
1665 && !labels.is_empty()
1666 {
1667 code_window_status = CodeWindowStatus::Closed;
1668 }
1669 let labels_len = labels.len();
1670 for (label_idx, (label, is_primary)) in labels.into_iter().enumerate() {
1671 let style = if is_primary {
1672 Style::LabelPrimary
1673 } else {
1674 Style::LabelSecondary
1675 };
1676 self.draw_col_separator_no_space(
1677 &mut buffer,
1678 line_idx,
1679 max_line_num_len + 1,
1680 );
1681 line_idx += 1;
1682 self.draw_note_separator(
1683 &mut buffer,
1684 line_idx,
1685 max_line_num_len + 1,
1686 label_idx != labels_len - 1,
1687 );
1688 buffer.append(line_idx, "note", Style::MainHeaderMsg);
1689 buffer.append(line_idx, ": ", Style::NoStyle);
1690 buffer.append(line_idx, label, style);
1691 line_idx += 1;
1692 }
1693 }
1694 }
1695 continue;
1696 } else {
1697 col_sep_before_no_show_source = true;
1698 }
1699 let is_primary = primary_lo.file.stable_id == annotated_file.file.stable_id;
1702 if is_primary {
1703 let loc = primary_lo.clone();
1704 if !self.short_message {
1705 let buffer_msg_line_offset = buffer.num_lines();
1707
1708 buffer.prepend(buffer_msg_line_offset, self.file_start(), Style::LineNumber);
1709 buffer.append(
1710 buffer_msg_line_offset,
1711 &format!(
1712 "{}:{}:{}",
1713 sm.filename_for_diagnostics(&loc.file.name),
1714 sm.doctest_offset_line(&loc.file.name, loc.line),
1715 loc.col.0 + 1,
1716 ),
1717 Style::LineAndColumn,
1718 );
1719 for _ in 0..max_line_num_len {
1720 buffer.prepend(buffer_msg_line_offset, " ", Style::NoStyle);
1721 }
1722 } else {
1723 buffer.prepend(
1724 0,
1725 &format!(
1726 "{}:{}:{}: ",
1727 sm.filename_for_diagnostics(&loc.file.name),
1728 sm.doctest_offset_line(&loc.file.name, loc.line),
1729 loc.col.0 + 1,
1730 ),
1731 Style::LineAndColumn,
1732 );
1733 }
1734 } else if !self.short_message {
1735 let buffer_msg_line_offset = buffer.num_lines();
1737
1738 self.draw_col_separator_no_space(
1749 &mut buffer,
1750 buffer_msg_line_offset,
1751 max_line_num_len + 1,
1752 );
1753
1754 buffer.prepend(
1756 buffer_msg_line_offset + 1,
1757 self.secondary_file_start(),
1758 Style::LineNumber,
1759 );
1760 let loc = if let Some(first_line) = annotated_file.lines.first() {
1761 let col = if let Some(first_annotation) = first_line.annotations.first() {
1762 format!(":{}", first_annotation.start_col.file + 1)
1763 } else {
1764 String::new()
1765 };
1766 format!(
1767 "{}:{}{}",
1768 sm.filename_for_diagnostics(&annotated_file.file.name),
1769 sm.doctest_offset_line(&annotated_file.file.name, first_line.line_index),
1770 col
1771 )
1772 } else {
1773 format!("{}", sm.filename_for_diagnostics(&annotated_file.file.name))
1774 };
1775 buffer.append(buffer_msg_line_offset + 1, &loc, Style::LineAndColumn);
1776 for _ in 0..max_line_num_len {
1777 buffer.prepend(buffer_msg_line_offset + 1, " ", Style::NoStyle);
1778 }
1779 }
1780
1781 if !self.short_message {
1782 let buffer_msg_line_offset = buffer.num_lines();
1784 self.draw_col_separator_no_space(
1785 &mut buffer,
1786 buffer_msg_line_offset,
1787 max_line_num_len + 1,
1788 );
1789
1790 let mut multilines = FxIndexMap::default();
1792
1793 let mut whitespace_margin = usize::MAX;
1795 for line_idx in 0..annotated_file.lines.len() {
1796 let file = Arc::clone(&annotated_file.file);
1797 let line = &annotated_file.lines[line_idx];
1798 if let Some(source_string) =
1799 line.line_index.checked_sub(1).and_then(|l| file.get_line(l))
1800 {
1801 let leading_whitespace = source_string
1808 .chars()
1809 .take_while(|c| rustc_lexer::is_whitespace(*c))
1810 .count();
1811 if source_string.chars().any(|c| !rustc_lexer::is_whitespace(c)) {
1812 whitespace_margin = min(whitespace_margin, leading_whitespace);
1813 }
1814 }
1815 }
1816 if whitespace_margin == usize::MAX {
1817 whitespace_margin = 0;
1818 }
1819
1820 let mut span_left_margin = usize::MAX;
1822 for line in &annotated_file.lines {
1823 for ann in &line.annotations {
1824 span_left_margin = min(span_left_margin, ann.start_col.file);
1825 span_left_margin = min(span_left_margin, ann.end_col.file);
1826 }
1827 }
1828 if span_left_margin == usize::MAX {
1829 span_left_margin = 0;
1830 }
1831
1832 let mut span_right_margin = 0;
1834 let mut label_right_margin = 0;
1835 let mut max_line_len = 0;
1836 for line in &annotated_file.lines {
1837 max_line_len = max(
1838 max_line_len,
1839 line.line_index
1840 .checked_sub(1)
1841 .and_then(|l| annotated_file.file.get_line(l))
1842 .map_or(0, |s| s.len()),
1843 );
1844 for ann in &line.annotations {
1845 span_right_margin = max(span_right_margin, ann.start_col.file);
1846 span_right_margin = max(span_right_margin, ann.end_col.file);
1847 let label_right = ann.label.as_ref().map_or(0, |l| l.len() + 1);
1849 label_right_margin =
1850 max(label_right_margin, ann.end_col.file + label_right);
1851 }
1852 }
1853
1854 let width_offset = 3 + max_line_num_len;
1855 let code_offset = if annotated_file.multiline_depth == 0 {
1856 width_offset
1857 } else {
1858 width_offset + annotated_file.multiline_depth + 1
1859 };
1860
1861 let column_width = self.column_width(code_offset);
1862
1863 let margin = Margin::new(
1864 whitespace_margin,
1865 span_left_margin,
1866 span_right_margin,
1867 label_right_margin,
1868 column_width,
1869 max_line_len,
1870 );
1871
1872 for line_idx in 0..annotated_file.lines.len() {
1874 let previous_buffer_line = buffer.num_lines();
1875
1876 let depths = self.render_source_line(
1877 &mut buffer,
1878 Arc::clone(&annotated_file.file),
1879 &annotated_file.lines[line_idx],
1880 width_offset,
1881 code_offset,
1882 margin,
1883 !is_cont
1884 && file_idx + 1 == annotated_files_len
1885 && line_idx + 1 == annotated_file.lines.len(),
1886 );
1887
1888 let mut to_add = FxIndexMap::default();
1889
1890 for (depth, style) in depths {
1891 if multilines.swap_remove(&depth).is_none() {
1893 to_add.insert(depth, style);
1894 }
1895 }
1896
1897 for (depth, style) in &multilines {
1900 for line in previous_buffer_line..buffer.num_lines() {
1901 self.draw_multiline_line(
1902 &mut buffer,
1903 line,
1904 width_offset,
1905 *depth,
1906 *style,
1907 );
1908 }
1909 }
1910 if line_idx < (annotated_file.lines.len() - 1) {
1913 let line_idx_delta = annotated_file.lines[line_idx + 1].line_index
1914 - annotated_file.lines[line_idx].line_index;
1915 if line_idx_delta > 2 {
1916 let last_buffer_line_num = buffer.num_lines();
1917 self.draw_line_separator(
1918 &mut buffer,
1919 last_buffer_line_num,
1920 width_offset,
1921 );
1922
1923 for (depth, style) in &multilines {
1925 self.draw_multiline_line(
1926 &mut buffer,
1927 last_buffer_line_num,
1928 width_offset,
1929 *depth,
1930 *style,
1931 );
1932 }
1933 if let Some(line) = annotated_file.lines.get(line_idx) {
1934 for ann in &line.annotations {
1935 if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1936 {
1937 self.draw_multiline_line(
1941 &mut buffer,
1942 last_buffer_line_num,
1943 width_offset,
1944 pos,
1945 if ann.is_primary {
1946 Style::UnderlinePrimary
1947 } else {
1948 Style::UnderlineSecondary
1949 },
1950 );
1951 }
1952 }
1953 }
1954 } else if line_idx_delta == 2 {
1955 let unannotated_line = annotated_file
1956 .file
1957 .get_line(annotated_file.lines[line_idx].line_index)
1958 .unwrap_or_else(|| Cow::from(""));
1959
1960 let last_buffer_line_num = buffer.num_lines();
1961
1962 self.draw_line(
1963 &mut buffer,
1964 &normalize_whitespace(&unannotated_line),
1965 annotated_file.lines[line_idx + 1].line_index - 1,
1966 last_buffer_line_num,
1967 width_offset,
1968 code_offset,
1969 margin,
1970 );
1971
1972 for (depth, style) in &multilines {
1973 self.draw_multiline_line(
1974 &mut buffer,
1975 last_buffer_line_num,
1976 width_offset,
1977 *depth,
1978 *style,
1979 );
1980 }
1981 if let Some(line) = annotated_file.lines.get(line_idx) {
1982 for ann in &line.annotations {
1983 if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1984 {
1985 self.draw_multiline_line(
1986 &mut buffer,
1987 last_buffer_line_num,
1988 width_offset,
1989 pos,
1990 if ann.is_primary {
1991 Style::UnderlinePrimary
1992 } else {
1993 Style::UnderlineSecondary
1994 },
1995 );
1996 }
1997 }
1998 }
1999 }
2000 }
2001
2002 multilines.extend(&to_add);
2003 }
2004 }
2005 trace!("buffer: {:#?}", buffer.render());
2006 }
2007
2008 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
2010
2011 Ok(code_window_status)
2012 }
2013
2014 fn column_width(&self, code_offset: usize) -> usize {
2015 if let Some(width) = self.diagnostic_width {
2016 width.saturating_sub(code_offset)
2017 } else if self.ui_testing || cfg!(miri) {
2018 DEFAULT_COLUMN_WIDTH.saturating_sub(code_offset)
2019 } else {
2020 termize::dimensions()
2021 .map(|(w, _)| w.saturating_sub(code_offset))
2022 .unwrap_or(DEFAULT_COLUMN_WIDTH)
2023 }
2024 }
2025
2026 fn emit_suggestion_default(
2027 &mut self,
2028 span: &MultiSpan,
2029 suggestion: &CodeSuggestion,
2030 args: &FluentArgs<'_>,
2031 level: &Level,
2032 max_line_num_len: usize,
2033 ) -> io::Result<()> {
2034 let Some(ref sm) = self.sm else {
2035 return Ok(());
2036 };
2037
2038 let suggestions = suggestion.splice_lines(sm);
2040 debug!(?suggestions);
2041
2042 if suggestions.is_empty() {
2043 return Ok(());
2049 }
2050
2051 let mut buffer = StyledBuffer::new();
2052
2053 buffer.append(0, level.to_str(), Style::Level(*level));
2055 buffer.append(0, ": ", Style::HeaderMsg);
2056
2057 let mut msg = vec![(suggestion.msg.to_owned(), Style::NoStyle)];
2058 if let Some(confusion_type) =
2059 suggestions.iter().take(MAX_SUGGESTIONS).find_map(|(_, _, _, confusion_type)| {
2060 if confusion_type.has_confusion() { Some(*confusion_type) } else { None }
2061 })
2062 {
2063 msg.push((confusion_type.label_text().into(), Style::NoStyle));
2064 }
2065 self.msgs_to_buffer(
2066 &mut buffer,
2067 &msg,
2068 args,
2069 max_line_num_len,
2070 "suggestion",
2071 Some(Style::HeaderMsg),
2072 );
2073
2074 let other_suggestions = suggestions.len().saturating_sub(MAX_SUGGESTIONS);
2075
2076 let mut row_num = 2;
2077 for (i, (complete, parts, highlights, _)) in
2078 suggestions.into_iter().enumerate().take(MAX_SUGGESTIONS)
2079 {
2080 debug!(?complete, ?parts, ?highlights);
2081
2082 let has_deletion =
2083 parts.iter().any(|p| p.is_deletion(sm) || p.is_destructive_replacement(sm));
2084 let is_multiline = complete.lines().count() > 1;
2085
2086 if i == 0 {
2087 self.draw_col_separator_start(&mut buffer, row_num - 1, max_line_num_len + 1);
2088 } else {
2089 buffer.puts(
2090 row_num - 1,
2091 max_line_num_len + 1,
2092 self.multi_suggestion_separator(),
2093 Style::LineNumber,
2094 );
2095 }
2096 if let Some(span) = span.primary_span() {
2097 let loc = sm.lookup_char_pos(parts[0].span.lo());
2102 if (span.is_dummy() || loc.file.name != sm.span_to_filename(span))
2103 && loc.file.name.is_real()
2104 {
2105 let arrow = self.file_start();
2108 buffer.puts(row_num - 1, 0, arrow, Style::LineNumber);
2109 let filename = sm.filename_for_diagnostics(&loc.file.name);
2110 let offset = sm.doctest_offset_line(&loc.file.name, loc.line);
2111 let message = format!("{}:{}:{}", filename, offset, loc.col.0 + 1);
2112 if row_num == 2 {
2113 let col = usize::max(max_line_num_len + 1, arrow.len());
2114 buffer.puts(1, col, &message, Style::LineAndColumn);
2115 } else {
2116 buffer.append(row_num - 1, &message, Style::LineAndColumn);
2117 }
2118 for _ in 0..max_line_num_len {
2119 buffer.prepend(row_num - 1, " ", Style::NoStyle);
2120 }
2121 self.draw_col_separator_no_space(&mut buffer, row_num, max_line_num_len + 1);
2122 row_num += 1;
2123 }
2124 }
2125 let show_code_change = if has_deletion && !is_multiline {
2126 DisplaySuggestion::Diff
2127 } else if let [part] = &parts[..]
2128 && part.snippet.ends_with('\n')
2129 && part.snippet.trim() == complete.trim()
2130 {
2131 DisplaySuggestion::Add
2133 } else if (parts.len() != 1 || parts[0].snippet.trim() != complete.trim())
2134 && !is_multiline
2135 {
2136 DisplaySuggestion::Underline
2137 } else {
2138 DisplaySuggestion::None
2139 };
2140
2141 if let DisplaySuggestion::Diff = show_code_change {
2142 row_num += 1;
2143 }
2144
2145 let file_lines = sm
2146 .span_to_lines(parts[0].span)
2147 .expect("span_to_lines failed when emitting suggestion");
2148
2149 assert!(!file_lines.lines.is_empty() || parts[0].span.is_dummy());
2150
2151 let line_start = sm.lookup_char_pos(parts[0].original_span.lo()).line;
2152 let mut lines = complete.lines();
2153 let lines_len = lines.clone().count();
2154 if lines.clone().next().is_none() {
2155 let line_end = sm.lookup_char_pos(parts[0].original_span.hi()).line;
2157 for line in line_start..=line_end {
2158 self.draw_line_num(
2159 &mut buffer,
2160 line,
2161 row_num - 1 + line - line_start,
2162 max_line_num_len,
2163 );
2164 buffer.puts(
2165 row_num - 1 + line - line_start,
2166 max_line_num_len + 1,
2167 "- ",
2168 Style::Removal,
2169 );
2170 buffer.puts(
2171 row_num - 1 + line - line_start,
2172 max_line_num_len + 3,
2173 &normalize_whitespace(&file_lines.file.get_line(line - 1).unwrap()),
2174 Style::Removal,
2175 );
2176 }
2177 row_num += line_end - line_start;
2178 }
2179 let mut unhighlighted_lines = Vec::new();
2180 let mut last_pos = 0;
2181 let mut is_item_attribute = false;
2182 for (line_pos, (line, highlight_parts)) in lines.by_ref().zip(highlights).enumerate() {
2183 last_pos = line_pos;
2184 debug!(%line_pos, %line, ?highlight_parts);
2185
2186 if highlight_parts.is_empty() {
2188 unhighlighted_lines.push((line_pos, line));
2189 continue;
2190 }
2191 if highlight_parts.len() == 1
2192 && line.trim().starts_with("#[")
2193 && line.trim().ends_with(']')
2194 && lines_len == 1
2195 {
2196 is_item_attribute = true;
2197 }
2198
2199 match unhighlighted_lines.len() {
2200 0 => (),
2201 n if n <= 3 => unhighlighted_lines.drain(..).for_each(|(p, l)| {
2206 self.draw_code_line(
2207 &mut buffer,
2208 &mut row_num,
2209 &[],
2210 p + line_start,
2211 l,
2212 show_code_change,
2213 max_line_num_len,
2214 &file_lines,
2215 is_multiline,
2216 )
2217 }),
2218 _ => {
2226 let last_line = unhighlighted_lines.pop();
2227 let first_line = unhighlighted_lines.drain(..).next();
2228
2229 if let Some((p, l)) = first_line {
2230 self.draw_code_line(
2231 &mut buffer,
2232 &mut row_num,
2233 &[],
2234 p + line_start,
2235 l,
2236 show_code_change,
2237 max_line_num_len,
2238 &file_lines,
2239 is_multiline,
2240 )
2241 }
2242
2243 let placeholder = self.margin();
2244 let padding = str_width(placeholder);
2245 buffer.puts(
2246 row_num,
2247 max_line_num_len.saturating_sub(padding),
2248 placeholder,
2249 Style::LineNumber,
2250 );
2251 row_num += 1;
2252
2253 if let Some((p, l)) = last_line {
2254 self.draw_code_line(
2255 &mut buffer,
2256 &mut row_num,
2257 &[],
2258 p + line_start,
2259 l,
2260 show_code_change,
2261 max_line_num_len,
2262 &file_lines,
2263 is_multiline,
2264 )
2265 }
2266 }
2267 }
2268
2269 self.draw_code_line(
2270 &mut buffer,
2271 &mut row_num,
2272 &highlight_parts,
2273 line_pos + line_start,
2274 line,
2275 show_code_change,
2276 max_line_num_len,
2277 &file_lines,
2278 is_multiline,
2279 )
2280 }
2281 if let DisplaySuggestion::Add = show_code_change
2282 && is_item_attribute
2283 {
2284 let file_lines = sm
2291 .span_to_lines(parts[0].span.shrink_to_hi())
2292 .expect("span_to_lines failed when emitting suggestion");
2293 let line_num = sm.lookup_char_pos(parts[0].span.lo()).line;
2294 if let Some(line) = file_lines.file.get_line(line_num - 1) {
2295 let line = normalize_whitespace(&line);
2296 self.draw_code_line(
2297 &mut buffer,
2298 &mut row_num,
2299 &[],
2300 line_num + last_pos + 1,
2301 &line,
2302 DisplaySuggestion::None,
2303 max_line_num_len,
2304 &file_lines,
2305 is_multiline,
2306 )
2307 }
2308 }
2309
2310 let mut offsets: Vec<(usize, isize)> = Vec::new();
2313 if let DisplaySuggestion::Diff | DisplaySuggestion::Underline | DisplaySuggestion::Add =
2316 show_code_change
2317 {
2318 for part in parts {
2319 let span_start_pos = sm.lookup_char_pos(part.span.lo()).col_display;
2320 let span_end_pos = sm.lookup_char_pos(part.span.hi()).col_display;
2321
2322 let is_whitespace_addition = part.snippet.trim().is_empty();
2325
2326 let start = if is_whitespace_addition {
2328 0
2329 } else {
2330 part.snippet.len().saturating_sub(part.snippet.trim_start().len())
2331 };
2332 let sub_len: usize = str_width(if is_whitespace_addition {
2335 &part.snippet
2336 } else {
2337 part.snippet.trim()
2338 });
2339
2340 let offset: isize = offsets
2341 .iter()
2342 .filter_map(
2343 |(start, v)| if span_start_pos < *start { None } else { Some(v) },
2344 )
2345 .sum();
2346 let underline_start = (span_start_pos + start) as isize + offset;
2347 let underline_end = (span_start_pos + start + sub_len) as isize + offset;
2348 assert!(underline_start >= 0 && underline_end >= 0);
2349 let padding: usize = max_line_num_len + 3;
2350 for p in underline_start..underline_end {
2351 if let DisplaySuggestion::Underline = show_code_change
2352 && is_different(sm, &part.snippet, part.span)
2353 {
2354 buffer.putc(
2357 row_num,
2358 (padding as isize + p) as usize,
2359 if part.is_addition(sm) { '+' } else { self.diff() },
2360 Style::Addition,
2361 );
2362 }
2363 }
2364 if let DisplaySuggestion::Diff = show_code_change {
2365 let snippet = sm.span_to_snippet(part.span).unwrap_or_default();
2395 let newlines = snippet.lines().count();
2396 if newlines > 0 && row_num > newlines {
2397 for (i, line) in snippet.lines().enumerate() {
2406 let line = normalize_whitespace(line);
2407 let row = (row_num - 2 - (newlines - i - 1)).max(2);
2408 let start = if i == 0 {
2414 (padding as isize + span_start_pos as isize) as usize
2415 } else {
2416 padding
2417 };
2418 let end = if i == 0 {
2419 (padding as isize
2420 + span_start_pos as isize
2421 + line.len() as isize)
2422 as usize
2423 } else if i == newlines - 1 {
2424 (padding as isize + span_end_pos as isize) as usize
2425 } else {
2426 (padding as isize + line.len() as isize) as usize
2427 };
2428 buffer.set_style_range(row, start, end, Style::Removal, true);
2429 }
2430 } else {
2431 buffer.set_style_range(
2433 row_num - 2,
2434 (padding as isize + span_start_pos as isize) as usize,
2435 (padding as isize + span_end_pos as isize) as usize,
2436 Style::Removal,
2437 true,
2438 );
2439 }
2440 }
2441
2442 let full_sub_len = str_width(&part.snippet) as isize;
2444
2445 let snippet_len = span_end_pos as isize - span_start_pos as isize;
2447 offsets.push((span_end_pos, full_sub_len - snippet_len));
2451 }
2452 row_num += 1;
2453 }
2454
2455 if lines.next().is_some() {
2457 let placeholder = self.margin();
2458 let padding = str_width(placeholder);
2459 buffer.puts(
2460 row_num,
2461 max_line_num_len.saturating_sub(padding),
2462 placeholder,
2463 Style::LineNumber,
2464 );
2465 } else {
2466 let row = match show_code_change {
2467 DisplaySuggestion::Diff
2468 | DisplaySuggestion::Add
2469 | DisplaySuggestion::Underline => row_num - 1,
2470 DisplaySuggestion::None => row_num,
2471 };
2472 if other_suggestions > 0 {
2473 self.draw_col_separator_no_space(&mut buffer, row, max_line_num_len + 1);
2474 } else {
2475 self.draw_col_separator_end(&mut buffer, row, max_line_num_len + 1);
2476 }
2477 row_num = row + 1;
2478 }
2479 }
2480 if other_suggestions > 0 {
2481 self.draw_note_separator(&mut buffer, row_num, max_line_num_len + 1, false);
2482 let msg = format!(
2483 "and {} other candidate{}",
2484 other_suggestions,
2485 pluralize!(other_suggestions)
2486 );
2487 buffer.append(row_num, &msg, Style::NoStyle);
2488 }
2489
2490 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
2491 Ok(())
2492 }
2493
2494 #[instrument(level = "trace", skip(self, args, code, children, suggestions))]
2495 fn emit_messages_default(
2496 &mut self,
2497 level: &Level,
2498 messages: &[(DiagMessage, Style)],
2499 args: &FluentArgs<'_>,
2500 code: &Option<ErrCode>,
2501 span: &MultiSpan,
2502 children: &[Subdiag],
2503 suggestions: &[CodeSuggestion],
2504 ) {
2505 let max_line_num_len = if self.ui_testing {
2506 ANONYMIZED_LINE_NUM.len()
2507 } else {
2508 let n = self.get_max_line_num(span, children);
2509 num_decimal_digits(n)
2510 };
2511
2512 match self.emit_messages_default_inner(
2513 span,
2514 messages,
2515 args,
2516 code,
2517 level,
2518 max_line_num_len,
2519 false,
2520 !children.is_empty()
2521 || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden),
2522 ) {
2523 Ok(code_window_status) => {
2524 if !children.is_empty()
2525 || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden)
2526 {
2527 let mut buffer = StyledBuffer::new();
2528 if !self.short_message {
2529 if let Some(child) = children.iter().next()
2530 && child.span.primary_spans().is_empty()
2531 {
2532 self.draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1);
2534 } else if matches!(code_window_status, CodeWindowStatus::Open) {
2535 self.draw_col_separator_end(&mut buffer, 0, max_line_num_len + 1);
2537 }
2538 }
2539 if let Err(e) = emit_to_destination(
2540 &buffer.render(),
2541 level,
2542 &mut self.dst,
2543 self.short_message,
2544 ) {
2545 panic!("failed to emit error: {e}")
2546 }
2547 }
2548 if !self.short_message {
2549 for (i, child) in children.iter().enumerate() {
2550 assert!(child.level.can_be_subdiag());
2551 let span = &child.span;
2552 let should_close = match children.get(i + 1) {
2554 Some(c) => !c.span.primary_spans().is_empty(),
2555 None => i + 1 == children.len(),
2556 };
2557 if let Err(err) = self.emit_messages_default_inner(
2558 span,
2559 &child.messages,
2560 args,
2561 &None,
2562 &child.level,
2563 max_line_num_len,
2564 true,
2565 !should_close,
2566 ) {
2567 panic!("failed to emit error: {err}");
2568 }
2569 }
2570 for (i, sugg) in suggestions.iter().enumerate() {
2571 match sugg.style {
2572 SuggestionStyle::CompletelyHidden => {
2573 }
2575 SuggestionStyle::HideCodeAlways => {
2576 if let Err(e) = self.emit_messages_default_inner(
2577 &MultiSpan::new(),
2578 &[(sugg.msg.to_owned(), Style::HeaderMsg)],
2579 args,
2580 &None,
2581 &Level::Help,
2582 max_line_num_len,
2583 true,
2584 i + 1 != suggestions.len(),
2587 ) {
2588 panic!("failed to emit error: {e}");
2589 }
2590 }
2591 SuggestionStyle::HideCodeInline
2592 | SuggestionStyle::ShowCode
2593 | SuggestionStyle::ShowAlways => {
2594 if let Err(e) = self.emit_suggestion_default(
2595 span,
2596 sugg,
2597 args,
2598 &Level::Help,
2599 max_line_num_len,
2600 ) {
2601 panic!("failed to emit error: {e}");
2602 }
2603 }
2604 }
2605 }
2606 }
2607 }
2608 Err(e) => panic!("failed to emit error: {e}"),
2609 }
2610
2611 match writeln!(self.dst) {
2612 Err(e) => panic!("failed to emit error: {e}"),
2613 _ => {
2614 if let Err(e) = self.dst.flush() {
2615 panic!("failed to emit error: {e}")
2616 }
2617 }
2618 }
2619 }
2620
2621 fn draw_code_line(
2622 &self,
2623 buffer: &mut StyledBuffer,
2624 row_num: &mut usize,
2625 highlight_parts: &[SubstitutionHighlight],
2626 line_num: usize,
2627 line_to_add: &str,
2628 show_code_change: DisplaySuggestion,
2629 max_line_num_len: usize,
2630 file_lines: &FileLines,
2631 is_multiline: bool,
2632 ) {
2633 if let DisplaySuggestion::Diff = show_code_change {
2634 let lines_to_remove = file_lines.lines.iter().take(file_lines.lines.len() - 1);
2637 for (index, line_to_remove) in lines_to_remove.enumerate() {
2638 self.draw_line_num(buffer, line_num + index, *row_num - 1, max_line_num_len);
2639 buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2640 let line = normalize_whitespace(
2641 &file_lines.file.get_line(line_to_remove.line_index).unwrap(),
2642 );
2643 buffer.puts(*row_num - 1, max_line_num_len + 3, &line, Style::NoStyle);
2644 *row_num += 1;
2645 }
2646 let last_line_index = file_lines.lines[file_lines.lines.len() - 1].line_index;
2653 let last_line = &file_lines.file.get_line(last_line_index).unwrap();
2654 if last_line != line_to_add {
2655 self.draw_line_num(
2656 buffer,
2657 line_num + file_lines.lines.len() - 1,
2658 *row_num - 1,
2659 max_line_num_len,
2660 );
2661 buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2662 buffer.puts(
2663 *row_num - 1,
2664 max_line_num_len + 3,
2665 &normalize_whitespace(last_line),
2666 Style::NoStyle,
2667 );
2668 if !line_to_add.trim().is_empty() {
2669 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2683 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2684 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2685 } else {
2686 *row_num -= 1;
2687 }
2688 } else {
2689 *row_num -= 2;
2690 }
2691 } else if is_multiline {
2692 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2693 match &highlight_parts {
2694 [SubstitutionHighlight { start: 0, end }] if *end == line_to_add.len() => {
2695 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2696 }
2697 [] | [SubstitutionHighlight { start: 0, end: 0 }] => {
2698 self.draw_col_separator_no_space(buffer, *row_num, max_line_num_len + 1);
2699 }
2700 _ => {
2701 let diff = self.diff();
2702 buffer.puts(
2703 *row_num,
2704 max_line_num_len + 1,
2705 &format!("{diff} "),
2706 Style::Addition,
2707 );
2708 }
2709 }
2710 buffer.puts(
2716 *row_num,
2717 max_line_num_len + 3,
2718 &normalize_whitespace(line_to_add),
2719 Style::NoStyle,
2720 );
2721 } else if let DisplaySuggestion::Add = show_code_change {
2722 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2723 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2724 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2725 } else {
2726 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2727 self.draw_col_separator(buffer, *row_num, max_line_num_len + 1);
2728 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2729 }
2730
2731 for &SubstitutionHighlight { start, end } in highlight_parts {
2733 if start != end {
2735 let tabs: usize = line_to_add
2737 .chars()
2738 .take(start)
2739 .map(|ch| match ch {
2740 '\t' => 3,
2741 _ => 0,
2742 })
2743 .sum();
2744 buffer.set_style_range(
2745 *row_num,
2746 max_line_num_len + 3 + start + tabs,
2747 max_line_num_len + 3 + end + tabs,
2748 Style::Addition,
2749 true,
2750 );
2751 }
2752 }
2753 *row_num += 1;
2754 }
2755
2756 fn underline(&self, is_primary: bool) -> UnderlineParts {
2757 match (self.theme, is_primary) {
2782 (OutputTheme::Ascii, true) => UnderlineParts {
2783 style: Style::UnderlinePrimary,
2784 underline: '^',
2785 label_start: '^',
2786 vertical_text_line: '|',
2787 multiline_vertical: '|',
2788 multiline_horizontal: '_',
2789 multiline_whole_line: '/',
2790 multiline_start_down: '^',
2791 bottom_right: '|',
2792 top_left: ' ',
2793 top_right_flat: '^',
2794 bottom_left: '|',
2795 multiline_end_up: '^',
2796 multiline_end_same_line: '^',
2797 multiline_bottom_right_with_text: '|',
2798 },
2799 (OutputTheme::Ascii, false) => UnderlineParts {
2800 style: Style::UnderlineSecondary,
2801 underline: '-',
2802 label_start: '-',
2803 vertical_text_line: '|',
2804 multiline_vertical: '|',
2805 multiline_horizontal: '_',
2806 multiline_whole_line: '/',
2807 multiline_start_down: '-',
2808 bottom_right: '|',
2809 top_left: ' ',
2810 top_right_flat: '-',
2811 bottom_left: '|',
2812 multiline_end_up: '-',
2813 multiline_end_same_line: '-',
2814 multiline_bottom_right_with_text: '|',
2815 },
2816 (OutputTheme::Unicode, true) => UnderlineParts {
2817 style: Style::UnderlinePrimary,
2818 underline: '━',
2819 label_start: '┯',
2820 vertical_text_line: '│',
2821 multiline_vertical: '┃',
2822 multiline_horizontal: '━',
2823 multiline_whole_line: '┏',
2824 multiline_start_down: '╿',
2825 bottom_right: '┙',
2826 top_left: '┏',
2827 top_right_flat: '┛',
2828 bottom_left: '┗',
2829 multiline_end_up: '╿',
2830 multiline_end_same_line: '┛',
2831 multiline_bottom_right_with_text: '┥',
2832 },
2833 (OutputTheme::Unicode, false) => UnderlineParts {
2834 style: Style::UnderlineSecondary,
2835 underline: '─',
2836 label_start: '┬',
2837 vertical_text_line: '│',
2838 multiline_vertical: '│',
2839 multiline_horizontal: '─',
2840 multiline_whole_line: '┌',
2841 multiline_start_down: '│',
2842 bottom_right: '┘',
2843 top_left: '┌',
2844 top_right_flat: '┘',
2845 bottom_left: '└',
2846 multiline_end_up: '│',
2847 multiline_end_same_line: '┘',
2848 multiline_bottom_right_with_text: '┤',
2849 },
2850 }
2851 }
2852
2853 fn col_separator(&self) -> char {
2854 match self.theme {
2855 OutputTheme::Ascii => '|',
2856 OutputTheme::Unicode => '│',
2857 }
2858 }
2859
2860 fn note_separator(&self, is_cont: bool) -> &'static str {
2861 match self.theme {
2862 OutputTheme::Ascii => "= ",
2863 OutputTheme::Unicode if is_cont => "├ ",
2864 OutputTheme::Unicode => "╰ ",
2865 }
2866 }
2867
2868 fn multi_suggestion_separator(&self) -> &'static str {
2869 match self.theme {
2870 OutputTheme::Ascii => "|",
2871 OutputTheme::Unicode => "├╴",
2872 }
2873 }
2874
2875 fn draw_col_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2876 let chr = self.col_separator();
2877 buffer.puts(line, col, &format!("{chr} "), Style::LineNumber);
2878 }
2879
2880 fn draw_col_separator_no_space(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2881 let chr = self.col_separator();
2882 self.draw_col_separator_no_space_with_style(buffer, chr, line, col, Style::LineNumber);
2883 }
2884
2885 fn draw_col_separator_start(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2886 match self.theme {
2887 OutputTheme::Ascii => {
2888 self.draw_col_separator_no_space_with_style(
2889 buffer,
2890 '|',
2891 line,
2892 col,
2893 Style::LineNumber,
2894 );
2895 }
2896 OutputTheme::Unicode => {
2897 self.draw_col_separator_no_space_with_style(
2898 buffer,
2899 '╭',
2900 line,
2901 col,
2902 Style::LineNumber,
2903 );
2904 self.draw_col_separator_no_space_with_style(
2905 buffer,
2906 '╴',
2907 line,
2908 col + 1,
2909 Style::LineNumber,
2910 );
2911 }
2912 }
2913 }
2914
2915 fn draw_col_separator_end(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2916 match self.theme {
2917 OutputTheme::Ascii => {
2918 self.draw_col_separator_no_space_with_style(
2919 buffer,
2920 '|',
2921 line,
2922 col,
2923 Style::LineNumber,
2924 );
2925 }
2926 OutputTheme::Unicode => {
2927 self.draw_col_separator_no_space_with_style(
2928 buffer,
2929 '╰',
2930 line,
2931 col,
2932 Style::LineNumber,
2933 );
2934 self.draw_col_separator_no_space_with_style(
2935 buffer,
2936 '╴',
2937 line,
2938 col + 1,
2939 Style::LineNumber,
2940 );
2941 }
2942 }
2943 }
2944
2945 fn draw_col_separator_no_space_with_style(
2946 &self,
2947 buffer: &mut StyledBuffer,
2948 chr: char,
2949 line: usize,
2950 col: usize,
2951 style: Style,
2952 ) {
2953 buffer.putc(line, col, chr, style);
2954 }
2955
2956 fn draw_range(
2957 &self,
2958 buffer: &mut StyledBuffer,
2959 symbol: char,
2960 line: usize,
2961 col_from: usize,
2962 col_to: usize,
2963 style: Style,
2964 ) {
2965 for col in col_from..col_to {
2966 buffer.putc(line, col, symbol, style);
2967 }
2968 }
2969
2970 fn draw_note_separator(
2971 &self,
2972 buffer: &mut StyledBuffer,
2973 line: usize,
2974 col: usize,
2975 is_cont: bool,
2976 ) {
2977 let chr = self.note_separator(is_cont);
2978 buffer.puts(line, col, chr, Style::LineNumber);
2979 }
2980
2981 fn draw_multiline_line(
2982 &self,
2983 buffer: &mut StyledBuffer,
2984 line: usize,
2985 offset: usize,
2986 depth: usize,
2987 style: Style,
2988 ) {
2989 let chr = match (style, self.theme) {
2990 (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Ascii) => '|',
2991 (_, OutputTheme::Ascii) => '|',
2992 (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Unicode) => '┃',
2993 (_, OutputTheme::Unicode) => '│',
2994 };
2995 buffer.putc(line, offset + depth - 1, chr, style);
2996 }
2997
2998 fn file_start(&self) -> &'static str {
2999 match self.theme {
3000 OutputTheme::Ascii => "--> ",
3001 OutputTheme::Unicode => " ╭▸ ",
3002 }
3003 }
3004
3005 fn secondary_file_start(&self) -> &'static str {
3006 match self.theme {
3007 OutputTheme::Ascii => "::: ",
3008 OutputTheme::Unicode => " ⸬ ",
3009 }
3010 }
3011
3012 fn diff(&self) -> char {
3013 match self.theme {
3014 OutputTheme::Ascii => '~',
3015 OutputTheme::Unicode => '±',
3016 }
3017 }
3018
3019 fn draw_line_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
3020 let (column, dots) = match self.theme {
3021 OutputTheme::Ascii => (0, "..."),
3022 OutputTheme::Unicode => (col - 2, "‡"),
3023 };
3024 buffer.puts(line, column, dots, Style::LineNumber);
3025 }
3026
3027 fn margin(&self) -> &'static str {
3028 match self.theme {
3029 OutputTheme::Ascii => "...",
3030 OutputTheme::Unicode => "…",
3031 }
3032 }
3033
3034 fn draw_line_num(
3035 &self,
3036 buffer: &mut StyledBuffer,
3037 line_num: usize,
3038 line_offset: usize,
3039 max_line_num_len: usize,
3040 ) {
3041 let line_num = self.maybe_anonymized(line_num);
3042 buffer.puts(
3043 line_offset,
3044 max_line_num_len.saturating_sub(str_width(&line_num)),
3045 &line_num,
3046 Style::LineNumber,
3047 );
3048 }
3049}
3050
3051#[derive(Debug, Clone, Copy)]
3052struct UnderlineParts {
3053 style: Style,
3054 underline: char,
3055 label_start: char,
3056 vertical_text_line: char,
3057 multiline_vertical: char,
3058 multiline_horizontal: char,
3059 multiline_whole_line: char,
3060 multiline_start_down: char,
3061 bottom_right: char,
3062 top_left: char,
3063 top_right_flat: char,
3064 bottom_left: char,
3065 multiline_end_up: char,
3066 multiline_end_same_line: char,
3067 multiline_bottom_right_with_text: char,
3068}
3069
3070#[derive(Clone, Copy, Debug)]
3071enum DisplaySuggestion {
3072 Underline,
3073 Diff,
3074 None,
3075 Add,
3076}
3077
3078#[derive(Clone, Copy, Debug)]
3079enum CodeWindowStatus {
3080 Closed,
3081 Open,
3082}
3083
3084impl FileWithAnnotatedLines {
3085 pub(crate) fn collect_annotations(
3088 emitter: &dyn Emitter,
3089 args: &FluentArgs<'_>,
3090 msp: &MultiSpan,
3091 ) -> Vec<FileWithAnnotatedLines> {
3092 fn add_annotation_to_file(
3093 file_vec: &mut Vec<FileWithAnnotatedLines>,
3094 file: Arc<SourceFile>,
3095 line_index: usize,
3096 ann: Annotation,
3097 ) {
3098 for slot in file_vec.iter_mut() {
3099 if slot.file.stable_id == file.stable_id {
3101 for line_slot in &mut slot.lines {
3103 if line_slot.line_index == line_index {
3104 line_slot.annotations.push(ann);
3105 return;
3106 }
3107 }
3108 slot.lines.push(Line { line_index, annotations: vec![ann] });
3110 slot.lines.sort();
3111 return;
3112 }
3113 }
3114 file_vec.push(FileWithAnnotatedLines {
3116 file,
3117 lines: vec![Line { line_index, annotations: vec![ann] }],
3118 multiline_depth: 0,
3119 });
3120 }
3121 let mut output = vec![];
3122 let mut multiline_annotations = vec![];
3123
3124 if let Some(sm) = emitter.source_map() {
3125 for SpanLabel { span, is_primary, label } in msp.span_labels() {
3126 let span = match (span.is_dummy(), msp.primary_span()) {
3129 (_, None) | (false, _) => span,
3130 (true, Some(span)) => span,
3131 };
3132
3133 let lo = sm.lookup_char_pos(span.lo());
3134 let mut hi = sm.lookup_char_pos(span.hi());
3135
3136 if lo.col_display == hi.col_display && lo.line == hi.line {
3143 hi.col_display += 1;
3144 }
3145
3146 let label = label.as_ref().map(|m| {
3147 normalize_whitespace(
3148 &emitter
3149 .translator()
3150 .translate_message(m, args)
3151 .map_err(Report::new)
3152 .unwrap(),
3153 )
3154 });
3155
3156 if lo.line != hi.line {
3157 let ml = MultilineAnnotation {
3158 depth: 1,
3159 line_start: lo.line,
3160 line_end: hi.line,
3161 start_col: AnnotationColumn::from_loc(&lo),
3162 end_col: AnnotationColumn::from_loc(&hi),
3163 is_primary,
3164 label,
3165 overlaps_exactly: false,
3166 };
3167 multiline_annotations.push((lo.file, ml));
3168 } else {
3169 let ann = Annotation {
3170 start_col: AnnotationColumn::from_loc(&lo),
3171 end_col: AnnotationColumn::from_loc(&hi),
3172 is_primary,
3173 label,
3174 annotation_type: AnnotationType::Singleline,
3175 };
3176 add_annotation_to_file(&mut output, lo.file, lo.line, ann);
3177 };
3178 }
3179 }
3180
3181 multiline_annotations.sort_by_key(|(_, ml)| (ml.line_start, usize::MAX - ml.line_end));
3183 for (_, ann) in multiline_annotations.clone() {
3184 for (_, a) in multiline_annotations.iter_mut() {
3185 if !(ann.same_span(a))
3188 && num_overlap(ann.line_start, ann.line_end, a.line_start, a.line_end, true)
3189 {
3190 a.increase_depth();
3191 } else if ann.same_span(a) && &ann != a {
3192 a.overlaps_exactly = true;
3193 } else {
3194 break;
3195 }
3196 }
3197 }
3198
3199 let mut max_depth = 0; for (_, ann) in &multiline_annotations {
3201 max_depth = max(max_depth, ann.depth);
3202 }
3203 for (_, a) in multiline_annotations.iter_mut() {
3205 a.depth = max_depth - a.depth + 1;
3206 }
3207 for (file, ann) in multiline_annotations {
3208 let mut end_ann = ann.as_end();
3209 if !ann.overlaps_exactly {
3210 add_annotation_to_file(
3233 &mut output,
3234 Arc::clone(&file),
3235 ann.line_start,
3236 ann.as_start(),
3237 );
3238 let middle = min(ann.line_start + 4, ann.line_end);
3243 let filter = |s: &str| {
3247 let s = s.trim();
3248 !(s.starts_with("//") && !(s.starts_with("///") || s.starts_with("//!")))
3250 && !["", "{", "}", "(", ")", "[", "]"].contains(&s)
3252 };
3253 let until = (ann.line_start..middle)
3254 .rev()
3255 .filter_map(|line| file.get_line(line - 1).map(|s| (line + 1, s)))
3256 .find(|(_, s)| filter(s))
3257 .map(|(line, _)| line)
3258 .unwrap_or(ann.line_start);
3259 for line in ann.line_start + 1..until {
3260 add_annotation_to_file(&mut output, Arc::clone(&file), line, ann.as_line());
3262 }
3263 let line_end = ann.line_end - 1;
3264 let end_is_empty = file.get_line(line_end - 1).is_some_and(|s| !filter(&s));
3265 if middle < line_end && !end_is_empty {
3266 add_annotation_to_file(&mut output, Arc::clone(&file), line_end, ann.as_line());
3267 }
3268 } else {
3269 end_ann.annotation_type = AnnotationType::Singleline;
3270 }
3271 add_annotation_to_file(&mut output, file, ann.line_end, end_ann);
3272 }
3273 for file_vec in output.iter_mut() {
3274 file_vec.multiline_depth = max_depth;
3275 }
3276 output
3277 }
3278}
3279
3280fn num_decimal_digits(num: usize) -> usize {
3285 #[cfg(target_pointer_width = "64")]
3286 const MAX_DIGITS: usize = 20;
3287
3288 #[cfg(target_pointer_width = "32")]
3289 const MAX_DIGITS: usize = 10;
3290
3291 #[cfg(target_pointer_width = "16")]
3292 const MAX_DIGITS: usize = 5;
3293
3294 let mut lim = 10;
3295 for num_digits in 1..MAX_DIGITS {
3296 if num < lim {
3297 return num_digits;
3298 }
3299 lim = lim.wrapping_mul(10);
3300 }
3301 MAX_DIGITS
3302}
3303
3304const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
3307 ('\0', "␀"),
3311 ('\u{0001}', "␁"),
3312 ('\u{0002}', "␂"),
3313 ('\u{0003}', "␃"),
3314 ('\u{0004}', "␄"),
3315 ('\u{0005}', "␅"),
3316 ('\u{0006}', "␆"),
3317 ('\u{0007}', "␇"),
3318 ('\u{0008}', "␈"),
3319 ('\t', " "), ('\u{000b}', "␋"),
3321 ('\u{000c}', "␌"),
3322 ('\u{000d}', "␍"),
3323 ('\u{000e}', "␎"),
3324 ('\u{000f}', "␏"),
3325 ('\u{0010}', "␐"),
3326 ('\u{0011}', "␑"),
3327 ('\u{0012}', "␒"),
3328 ('\u{0013}', "␓"),
3329 ('\u{0014}', "␔"),
3330 ('\u{0015}', "␕"),
3331 ('\u{0016}', "␖"),
3332 ('\u{0017}', "␗"),
3333 ('\u{0018}', "␘"),
3334 ('\u{0019}', "␙"),
3335 ('\u{001a}', "␚"),
3336 ('\u{001b}', "␛"),
3337 ('\u{001c}', "␜"),
3338 ('\u{001d}', "␝"),
3339 ('\u{001e}', "␞"),
3340 ('\u{001f}', "␟"),
3341 ('\u{007f}', "␡"),
3342 ('\u{200d}', ""), ('\u{202a}', "�"), ('\u{202b}', "�"), ('\u{202c}', "�"), ('\u{202d}', "�"),
3347 ('\u{202e}', "�"),
3348 ('\u{2066}', "�"),
3349 ('\u{2067}', "�"),
3350 ('\u{2068}', "�"),
3351 ('\u{2069}', "�"),
3352];
3353
3354pub(crate) fn normalize_whitespace(s: &str) -> String {
3355 const {
3356 let mut i = 1;
3357 while i < OUTPUT_REPLACEMENTS.len() {
3358 assert!(
3359 OUTPUT_REPLACEMENTS[i - 1].0 < OUTPUT_REPLACEMENTS[i].0,
3360 "The OUTPUT_REPLACEMENTS array must be sorted (for binary search to work) \
3361 and must contain no duplicate entries"
3362 );
3363 i += 1;
3364 }
3365 }
3366 s.chars().fold(String::with_capacity(s.len()), |mut s, c| {
3370 match OUTPUT_REPLACEMENTS.binary_search_by_key(&c, |(k, _)| *k) {
3371 Ok(i) => s.push_str(OUTPUT_REPLACEMENTS[i].1),
3372 _ => s.push(c),
3373 }
3374 s
3375 })
3376}
3377
3378fn num_overlap(
3379 a_start: usize,
3380 a_end: usize,
3381 b_start: usize,
3382 b_end: usize,
3383 inclusive: bool,
3384) -> bool {
3385 let extra = if inclusive { 1 } else { 0 };
3386 (b_start..b_end + extra).contains(&a_start) || (a_start..a_end + extra).contains(&b_start)
3387}
3388
3389fn overlaps(a1: &Annotation, a2: &Annotation, padding: usize) -> bool {
3390 num_overlap(
3391 a1.start_col.display,
3392 a1.end_col.display + padding,
3393 a2.start_col.display,
3394 a2.end_col.display,
3395 false,
3396 )
3397}
3398
3399pub(crate) fn emit_to_destination(
3400 rendered_buffer: &[Vec<StyledString>],
3401 lvl: &Level,
3402 dst: &mut Destination,
3403 short_message: bool,
3404) -> io::Result<()> {
3405 use crate::lock;
3406
3407 let _buffer_lock = lock::acquire_global_lock("rustc_errors");
3420 for (pos, line) in rendered_buffer.iter().enumerate() {
3421 for part in line {
3422 let style = part.style.anstyle(*lvl);
3423 write!(dst, "{style}{}{style:#}", part.text)?;
3424 }
3425 if !short_message && (!lvl.is_failure_note() || pos != rendered_buffer.len() - 1) {
3426 writeln!(dst)?;
3427 }
3428 }
3429 dst.flush()?;
3430 Ok(())
3431}
3432
3433pub type Destination = AutoStream<Box<dyn Write + Send>>;
3434
3435struct Buffy {
3436 buffer_writer: std::io::Stderr,
3437 buffer: Vec<u8>,
3438}
3439
3440impl Write for Buffy {
3441 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
3442 self.buffer.write(buf)
3443 }
3444
3445 fn flush(&mut self) -> io::Result<()> {
3446 self.buffer_writer.write_all(&self.buffer)?;
3447 self.buffer.clear();
3448 Ok(())
3449 }
3450}
3451
3452impl Drop for Buffy {
3453 fn drop(&mut self) {
3454 if !self.buffer.is_empty() {
3455 self.flush().unwrap();
3456 panic!("buffers need to be flushed in order to print their contents");
3457 }
3458 }
3459}
3460
3461pub fn stderr_destination(color: ColorConfig) -> Destination {
3462 let buffer_writer = std::io::stderr();
3463 let choice = get_stderr_color_choice(color, &buffer_writer);
3466 if cfg!(windows) {
3473 AutoStream::new(Box::new(buffer_writer), choice)
3474 } else {
3475 let buffer = Vec::new();
3476 AutoStream::new(Box::new(Buffy { buffer_writer, buffer }), choice)
3477 }
3478}
3479
3480pub fn get_stderr_color_choice(color: ColorConfig, stderr: &std::io::Stderr) -> ColorChoice {
3481 let choice = color.to_color_choice();
3482 if matches!(choice, ColorChoice::Auto) { AutoStream::choice(stderr) } else { choice }
3483}
3484
3485const BRIGHT_BLUE: anstyle::Style = if cfg!(windows) {
3489 AnsiColor::BrightCyan.on_default()
3490} else {
3491 AnsiColor::BrightBlue.on_default()
3492};
3493
3494impl Style {
3495 pub(crate) fn anstyle(&self, lvl: Level) -> anstyle::Style {
3496 match self {
3497 Style::Addition => AnsiColor::BrightGreen.on_default(),
3498 Style::Removal => AnsiColor::BrightRed.on_default(),
3499 Style::LineAndColumn => anstyle::Style::new(),
3500 Style::LineNumber => BRIGHT_BLUE.effects(Effects::BOLD),
3501 Style::Quotation => anstyle::Style::new(),
3502 Style::MainHeaderMsg => if cfg!(windows) {
3503 AnsiColor::BrightWhite.on_default()
3504 } else {
3505 anstyle::Style::new()
3506 }
3507 .effects(Effects::BOLD),
3508 Style::UnderlinePrimary | Style::LabelPrimary => lvl.color().effects(Effects::BOLD),
3509 Style::UnderlineSecondary | Style::LabelSecondary => BRIGHT_BLUE.effects(Effects::BOLD),
3510 Style::HeaderMsg | Style::NoStyle => anstyle::Style::new(),
3511 Style::Level(lvl) => lvl.color().effects(Effects::BOLD),
3512 Style::Highlight => AnsiColor::Magenta.on_default().effects(Effects::BOLD),
3513 }
3514 }
3515}
3516
3517pub fn is_different(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
3519 let found = match sm.span_to_snippet(sp) {
3520 Ok(snippet) => snippet,
3521 Err(e) => {
3522 warn!(error = ?e, "Invalid span {:?}", sp);
3523 return true;
3524 }
3525 };
3526 found != suggested
3527}
3528
3529pub fn detect_confusion_type(sm: &SourceMap, suggested: &str, sp: Span) -> ConfusionType {
3531 let found = match sm.span_to_snippet(sp) {
3532 Ok(snippet) => snippet,
3533 Err(e) => {
3534 warn!(error = ?e, "Invalid span {:?}", sp);
3535 return ConfusionType::None;
3536 }
3537 };
3538
3539 let mut has_case_confusion = false;
3540 let mut has_digit_letter_confusion = false;
3541
3542 if found.len() == suggested.len() {
3543 let mut has_case_diff = false;
3544 let mut has_digit_letter_confusable = false;
3545 let mut has_other_diff = false;
3546
3547 let ascii_confusables = &['c', 'f', 'i', 'k', 'o', 's', 'u', 'v', 'w', 'x', 'y', 'z'];
3548
3549 let digit_letter_confusables = [('0', 'O'), ('1', 'l'), ('5', 'S'), ('8', 'B'), ('9', 'g')];
3550
3551 for (f, s) in iter::zip(found.chars(), suggested.chars()) {
3552 if f != s {
3553 if f.eq_ignore_ascii_case(&s) {
3554 if ascii_confusables.contains(&f) || ascii_confusables.contains(&s) {
3556 has_case_diff = true;
3557 } else {
3558 has_other_diff = true;
3559 }
3560 } else if digit_letter_confusables.contains(&(f, s))
3561 || digit_letter_confusables.contains(&(s, f))
3562 {
3563 has_digit_letter_confusable = true;
3565 } else {
3566 has_other_diff = true;
3567 }
3568 }
3569 }
3570
3571 if has_case_diff && !has_other_diff && found != suggested {
3573 has_case_confusion = true;
3574 }
3575 if has_digit_letter_confusable && !has_other_diff && found != suggested {
3576 has_digit_letter_confusion = true;
3577 }
3578 }
3579
3580 match (has_case_confusion, has_digit_letter_confusion) {
3581 (true, true) => ConfusionType::Both,
3582 (true, false) => ConfusionType::Case,
3583 (false, true) => ConfusionType::DigitLetter,
3584 (false, false) => ConfusionType::None,
3585 }
3586}
3587
3588#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3590pub enum ConfusionType {
3591 None,
3593 Case,
3595 DigitLetter,
3597 Both,
3599}
3600
3601impl ConfusionType {
3602 pub fn label_text(&self) -> &'static str {
3604 match self {
3605 ConfusionType::None => "",
3606 ConfusionType::Case => " (notice the capitalization)",
3607 ConfusionType::DigitLetter => " (notice the digit/letter confusion)",
3608 ConfusionType::Both => " (notice the capitalization and digit/letter confusion)",
3609 }
3610 }
3611
3612 pub fn combine(self, other: ConfusionType) -> ConfusionType {
3616 match (self, other) {
3617 (ConfusionType::None, other) => other,
3618 (this, ConfusionType::None) => this,
3619 (ConfusionType::Both, _) | (_, ConfusionType::Both) => ConfusionType::Both,
3620 (ConfusionType::Case, ConfusionType::DigitLetter)
3621 | (ConfusionType::DigitLetter, ConfusionType::Case) => ConfusionType::Both,
3622 (ConfusionType::Case, ConfusionType::Case) => ConfusionType::Case,
3623 (ConfusionType::DigitLetter, ConfusionType::DigitLetter) => ConfusionType::DigitLetter,
3624 }
3625 }
3626
3627 pub fn has_confusion(&self) -> bool {
3629 *self != ConfusionType::None
3630 }
3631}
3632
3633pub(crate) fn should_show_source_code(
3634 ignored_directories: &[String],
3635 sm: &SourceMap,
3636 file: &SourceFile,
3637) -> bool {
3638 if !sm.ensure_source_file_source_present(file) {
3639 return false;
3640 }
3641
3642 let FileName::Real(name) = &file.name else { return true };
3643 name.local_path()
3644 .map(|path| ignored_directories.iter().all(|dir| !path.starts_with(dir)))
3645 .unwrap_or(true)
3646}