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 mut span = sp;
478 while let Some(callsite) = span.parent_callsite() {
479 span = callsite;
480 if !source_map.is_imported(span) {
481 return Some((sp, span));
482 }
483 }
484 }
485 None
486 })
487 .collect();
488
489 for (from, to) in replacements {
491 span.replace(from, to);
492 }
493 }
494}
495
496impl Emitter for HumanEmitter {
497 fn source_map(&self) -> Option<&SourceMap> {
498 self.sm.as_deref()
499 }
500
501 fn emit_diagnostic(&mut self, mut diag: DiagInner, _registry: &Registry) {
502 let fluent_args = to_fluent_args(diag.args.iter());
503
504 if self.track_diagnostics && diag.span.has_primary_spans() && !diag.span.is_dummy() {
505 diag.children.insert(0, diag.emitted_at_sub_diag());
506 }
507
508 let mut suggestions = diag.suggestions.unwrap_tag();
509 self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args);
510
511 self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
512 &mut diag.span,
513 &mut diag.children,
514 &diag.level,
515 self.macro_backtrace,
516 );
517
518 self.emit_messages_default(
519 &diag.level,
520 &diag.messages,
521 &fluent_args,
522 &diag.code,
523 &diag.span,
524 &diag.children,
525 &suggestions,
526 );
527 }
528
529 fn should_show_explain(&self) -> bool {
530 !self.short_message
531 }
532
533 fn translator(&self) -> &Translator {
534 &self.translator
535 }
536}
537
538pub struct EmitterWithNote {
540 pub emitter: Box<dyn Emitter + DynSend>,
541 pub note: String,
542}
543
544impl Emitter for EmitterWithNote {
545 fn source_map(&self) -> Option<&SourceMap> {
546 None
547 }
548
549 fn emit_diagnostic(&mut self, mut diag: DiagInner, registry: &Registry) {
550 diag.sub(Level::Note, self.note.clone(), MultiSpan::new());
551 self.emitter.emit_diagnostic(diag, registry);
552 }
553
554 fn translator(&self) -> &Translator {
555 self.emitter.translator()
556 }
557}
558
559pub struct SilentEmitter {
560 pub translator: Translator,
561}
562
563impl Emitter for SilentEmitter {
564 fn source_map(&self) -> Option<&SourceMap> {
565 None
566 }
567
568 fn emit_diagnostic(&mut self, _diag: DiagInner, _registry: &Registry) {}
569
570 fn translator(&self) -> &Translator {
571 &self.translator
572 }
573}
574
575pub const MAX_SUGGESTIONS: usize = 4;
579
580#[derive(Clone, Copy, Debug, PartialEq, Eq)]
581pub enum ColorConfig {
582 Auto,
583 Always,
584 Never,
585}
586
587impl ColorConfig {
588 pub fn to_color_choice(self) -> ColorChoice {
589 match self {
590 ColorConfig::Always => {
591 if io::stderr().is_terminal() {
592 ColorChoice::Always
593 } else {
594 ColorChoice::AlwaysAnsi
595 }
596 }
597 ColorConfig::Never => ColorChoice::Never,
598 ColorConfig::Auto if io::stderr().is_terminal() => ColorChoice::Auto,
599 ColorConfig::Auto => ColorChoice::Never,
600 }
601 }
602}
603
604#[derive(Debug, Clone, Copy, PartialEq, Eq)]
605pub enum OutputTheme {
606 Ascii,
607 Unicode,
608}
609
610#[derive(Setters)]
612pub struct HumanEmitter {
613 #[setters(skip)]
614 dst: IntoDynSyncSend<Destination>,
615 sm: Option<Arc<SourceMap>>,
616 #[setters(skip)]
617 translator: Translator,
618 short_message: bool,
619 ui_testing: bool,
620 ignored_directories_in_source_blocks: Vec<String>,
621 diagnostic_width: Option<usize>,
622
623 macro_backtrace: bool,
624 track_diagnostics: bool,
625 terminal_url: TerminalUrl,
626 theme: OutputTheme,
627}
628
629#[derive(Debug)]
630pub(crate) struct FileWithAnnotatedLines {
631 pub(crate) file: Arc<SourceFile>,
632 pub(crate) lines: Vec<Line>,
633 multiline_depth: usize,
634}
635
636impl HumanEmitter {
637 pub fn new(dst: Destination, translator: Translator) -> HumanEmitter {
638 HumanEmitter {
639 dst: IntoDynSyncSend(dst),
640 sm: None,
641 translator,
642 short_message: false,
643 ui_testing: false,
644 ignored_directories_in_source_blocks: Vec::new(),
645 diagnostic_width: None,
646 macro_backtrace: false,
647 track_diagnostics: false,
648 terminal_url: TerminalUrl::No,
649 theme: OutputTheme::Ascii,
650 }
651 }
652
653 fn maybe_anonymized(&self, line_num: usize) -> Cow<'static, str> {
654 if self.ui_testing {
655 Cow::Borrowed(ANONYMIZED_LINE_NUM)
656 } else {
657 Cow::Owned(line_num.to_string())
658 }
659 }
660
661 fn draw_line(
662 &self,
663 buffer: &mut StyledBuffer,
664 source_string: &str,
665 line_index: usize,
666 line_offset: usize,
667 width_offset: usize,
668 code_offset: usize,
669 margin: Margin,
670 ) -> usize {
671 let line_len = source_string.len();
672 let left = margin.left(line_len);
674 let right = margin.right(line_len);
675 let code: String = source_string
678 .chars()
679 .enumerate()
680 .skip_while(|(i, _)| *i < left)
681 .take_while(|(i, _)| *i < right)
682 .map(|(_, c)| c)
683 .collect();
684 let code = normalize_whitespace(&code);
685 let was_cut_right =
686 source_string.chars().enumerate().skip_while(|(i, _)| *i < right).next().is_some();
687 buffer.puts(line_offset, code_offset, &code, Style::Quotation);
688 let placeholder = self.margin();
689 if margin.was_cut_left() {
690 buffer.puts(line_offset, code_offset, placeholder, Style::LineNumber);
692 }
693 if was_cut_right {
694 let padding = str_width(placeholder);
695 buffer.puts(
697 line_offset,
698 code_offset + str_width(&code) - padding,
699 placeholder,
700 Style::LineNumber,
701 );
702 }
703 self.draw_line_num(buffer, line_index, line_offset, width_offset - 3);
704 self.draw_col_separator_no_space(buffer, line_offset, width_offset - 2);
705 left
706 }
707
708 #[instrument(level = "trace", skip(self), ret)]
709 fn render_source_line(
710 &self,
711 buffer: &mut StyledBuffer,
712 file: Arc<SourceFile>,
713 line: &Line,
714 width_offset: usize,
715 code_offset: usize,
716 margin: Margin,
717 close_window: bool,
718 ) -> Vec<(usize, Style)> {
719 if line.line_index == 0 {
734 return Vec::new();
735 }
736
737 let Some(source_string) = file.get_line(line.line_index - 1) else {
738 return Vec::new();
739 };
740 trace!(?source_string);
741
742 let line_offset = buffer.num_lines();
743
744 let left = self.draw_line(
747 buffer,
748 &source_string,
749 line.line_index,
750 line_offset,
751 width_offset,
752 code_offset,
753 margin,
754 );
755
756 let mut buffer_ops = vec![];
773 let mut annotations = vec![];
774 let mut short_start = true;
775 for ann in &line.annotations {
776 if let AnnotationType::MultilineStart(depth) = ann.annotation_type {
777 if source_string.chars().take(ann.start_col.file).all(|c| c.is_whitespace()) {
778 let uline = self.underline(ann.is_primary);
779 let chr = uline.multiline_whole_line;
780 annotations.push((depth, uline.style));
781 buffer_ops.push((line_offset, width_offset + depth - 1, chr, uline.style));
782 } else {
783 short_start = false;
784 break;
785 }
786 } else if let AnnotationType::MultilineLine(_) = ann.annotation_type {
787 } else {
788 short_start = false;
789 break;
790 }
791 }
792 if short_start {
793 for (y, x, c, s) in buffer_ops {
794 buffer.putc(y, x, c, s);
795 }
796 return annotations;
797 }
798
799 let mut annotations = line.annotations.clone();
832 annotations.sort_by_key(|a| Reverse(a.start_col));
833
834 let mut overlap = vec![false; annotations.len()];
897 let mut annotations_position = vec![];
898 let mut line_len: usize = 0;
899 let mut p = 0;
900 for (i, annotation) in annotations.iter().enumerate() {
901 for (j, next) in annotations.iter().enumerate() {
902 if overlaps(next, annotation, 0) && j > i {
903 overlap[i] = true;
904 overlap[j] = true;
905 }
906 if overlaps(next, annotation, 0) && annotation.has_label() && j > i && p == 0
910 {
912 if next.start_col == annotation.start_col
915 && next.end_col == annotation.end_col
916 && !next.has_label()
917 {
918 continue;
919 }
920
921 p += 1;
923 break;
924 }
925 }
926 annotations_position.push((p, annotation));
927 for (j, next) in annotations.iter().enumerate() {
928 if j > i {
929 let l = next.label.as_ref().map_or(0, |label| label.len() + 2);
930 if (overlaps(next, annotation, l) && annotation.has_label() && next.has_label()) || (annotation.takes_space() && next.has_label()) || (annotation.has_label() && next.takes_space())
947 || (annotation.takes_space() && next.takes_space())
948 || (overlaps(next, annotation, l)
949 && next.end_col <= annotation.end_col
950 && next.has_label()
951 && p == 0)
952 {
954 p += 1;
956 break;
957 }
958 }
959 }
960 line_len = max(line_len, p);
961 }
962
963 if line_len != 0 {
964 line_len += 1;
965 }
966
967 if line.annotations.iter().all(|a| a.is_line()) {
970 return vec![];
971 }
972
973 if annotations_position
974 .iter()
975 .all(|(_, ann)| matches!(ann.annotation_type, AnnotationType::MultilineStart(_)))
976 && let Some(max_pos) = annotations_position.iter().map(|(pos, _)| *pos).max()
977 {
978 for (pos, _) in &mut annotations_position {
991 *pos = max_pos - *pos;
992 }
993 line_len = line_len.saturating_sub(1);
996 }
997
998 for pos in 0..=line_len {
1010 self.draw_col_separator_no_space(buffer, line_offset + pos + 1, width_offset - 2);
1011 }
1012 if close_window {
1013 self.draw_col_separator_end(buffer, line_offset + line_len + 1, width_offset - 2);
1014 }
1015
1016 for &(pos, annotation) in &annotations_position {
1029 let underline = self.underline(annotation.is_primary);
1030 let pos = pos + 1;
1031 match annotation.annotation_type {
1032 AnnotationType::MultilineStart(depth) | AnnotationType::MultilineEnd(depth) => {
1033 let pre: usize = source_string
1034 .chars()
1035 .take(annotation.start_col.file)
1036 .skip(left)
1037 .map(|c| char_width(c))
1038 .sum();
1039 self.draw_range(
1040 buffer,
1041 underline.multiline_horizontal,
1042 line_offset + pos,
1043 width_offset + depth,
1044 code_offset + pre,
1045 underline.style,
1046 );
1047 }
1048 _ => {}
1049 }
1050 }
1051
1052 for &(pos, annotation) in &annotations_position {
1064 let underline = self.underline(annotation.is_primary);
1065 let pos = pos + 1;
1066
1067 let code_offset = code_offset
1068 + source_string
1069 .chars()
1070 .take(annotation.start_col.file)
1071 .skip(left)
1072 .map(|c| char_width(c))
1073 .sum::<usize>();
1074 if pos > 1 && (annotation.has_label() || annotation.takes_space()) {
1075 for p in line_offset + 1..=line_offset + pos {
1076 buffer.putc(
1077 p,
1078 code_offset,
1079 match annotation.annotation_type {
1080 AnnotationType::MultilineLine(_) => underline.multiline_vertical,
1081 _ => underline.vertical_text_line,
1082 },
1083 underline.style,
1084 );
1085 }
1086 if let AnnotationType::MultilineStart(_) = annotation.annotation_type {
1087 buffer.putc(
1088 line_offset + pos,
1089 code_offset,
1090 underline.bottom_right,
1091 underline.style,
1092 );
1093 }
1094 if let AnnotationType::MultilineEnd(_) = annotation.annotation_type
1095 && annotation.has_label()
1096 {
1097 buffer.putc(
1098 line_offset + pos,
1099 code_offset,
1100 underline.multiline_bottom_right_with_text,
1101 underline.style,
1102 );
1103 }
1104 }
1105 match annotation.annotation_type {
1106 AnnotationType::MultilineStart(depth) => {
1107 buffer.putc(
1108 line_offset + pos,
1109 width_offset + depth - 1,
1110 underline.top_left,
1111 underline.style,
1112 );
1113 for p in line_offset + pos + 1..line_offset + line_len + 2 {
1114 buffer.putc(
1115 p,
1116 width_offset + depth - 1,
1117 underline.multiline_vertical,
1118 underline.style,
1119 );
1120 }
1121 }
1122 AnnotationType::MultilineEnd(depth) => {
1123 for p in line_offset..line_offset + pos {
1124 buffer.putc(
1125 p,
1126 width_offset + depth - 1,
1127 underline.multiline_vertical,
1128 underline.style,
1129 );
1130 }
1131 buffer.putc(
1132 line_offset + pos,
1133 width_offset + depth - 1,
1134 underline.bottom_left,
1135 underline.style,
1136 );
1137 }
1138 _ => (),
1139 }
1140 }
1141
1142 for &(pos, annotation) in &annotations_position {
1154 let style =
1155 if annotation.is_primary { Style::LabelPrimary } else { Style::LabelSecondary };
1156 let (pos, col) = if pos == 0 {
1157 let pre: usize = source_string
1158 .chars()
1159 .take(annotation.end_col.file)
1160 .skip(left)
1161 .map(|c| char_width(c))
1162 .sum();
1163 if annotation.end_col.file == 0 {
1164 (pos + 1, (pre + 2))
1165 } else {
1166 let pad = if annotation.end_col.file - annotation.start_col.file == 0 {
1167 2
1168 } else {
1169 1
1170 };
1171 (pos + 1, (pre + pad))
1172 }
1173 } else {
1174 let pre: usize = source_string
1175 .chars()
1176 .take(annotation.start_col.file)
1177 .skip(left)
1178 .map(|c| char_width(c))
1179 .sum();
1180 (pos + 2, pre)
1181 };
1182 if let Some(ref label) = annotation.label {
1183 buffer.puts(line_offset + pos, code_offset + col, label, style);
1184 }
1185 }
1186
1187 annotations_position.sort_by_key(|(_, ann)| {
1196 (Reverse(ann.len()), ann.is_primary)
1198 });
1199
1200 for &(pos, annotation) in &annotations_position {
1212 let uline = self.underline(annotation.is_primary);
1213 let width = annotation.end_col.file - annotation.start_col.file;
1214 let previous: String =
1215 source_string.chars().take(annotation.start_col.file).skip(left).collect();
1216 let underlined: String =
1217 source_string.chars().skip(annotation.start_col.file).take(width).collect();
1218 debug!(?previous, ?underlined);
1219 let code_offset = code_offset
1220 + source_string
1221 .chars()
1222 .take(annotation.start_col.file)
1223 .skip(left)
1224 .map(|c| char_width(c))
1225 .sum::<usize>();
1226 let ann_width: usize = source_string
1227 .chars()
1228 .skip(annotation.start_col.file)
1229 .take(width)
1230 .map(|c| char_width(c))
1231 .sum();
1232 let ann_width = if ann_width == 0
1233 && matches!(annotation.annotation_type, AnnotationType::Singleline)
1234 {
1235 1
1236 } else {
1237 ann_width
1238 };
1239 for p in 0..ann_width {
1240 buffer.putc(line_offset + 1, code_offset + p, uline.underline, uline.style);
1242 }
1243
1244 if pos == 0
1245 && matches!(
1246 annotation.annotation_type,
1247 AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_)
1248 )
1249 {
1250 buffer.putc(
1252 line_offset + 1,
1253 code_offset,
1254 match annotation.annotation_type {
1255 AnnotationType::MultilineStart(_) => uline.top_right_flat,
1256 AnnotationType::MultilineEnd(_) => uline.multiline_end_same_line,
1257 _ => panic!("unexpected annotation type: {annotation:?}"),
1258 },
1259 uline.style,
1260 );
1261 } else if pos != 0
1262 && matches!(
1263 annotation.annotation_type,
1264 AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_)
1265 )
1266 {
1267 buffer.putc(
1270 line_offset + 1,
1271 code_offset,
1272 match annotation.annotation_type {
1273 AnnotationType::MultilineStart(_) => uline.multiline_start_down,
1274 AnnotationType::MultilineEnd(_) => uline.multiline_end_up,
1275 _ => panic!("unexpected annotation type: {annotation:?}"),
1276 },
1277 uline.style,
1278 );
1279 } else if pos != 0 && annotation.has_label() {
1280 buffer.putc(line_offset + 1, code_offset, uline.label_start, uline.style);
1282 }
1283 }
1284
1285 for (i, (_pos, annotation)) in annotations_position.iter().enumerate() {
1289 if overlap[i] {
1291 continue;
1292 };
1293 let AnnotationType::Singleline = annotation.annotation_type else { continue };
1294 let width = annotation.end_col.display - annotation.start_col.display;
1295 if width > margin.column_width * 2 && width > 10 {
1296 let pad = max(margin.column_width / 3, 5);
1299 buffer.replace(
1301 line_offset,
1302 annotation.start_col.file + pad,
1303 annotation.end_col.file - pad,
1304 self.margin(),
1305 );
1306 buffer.replace(
1308 line_offset + 1,
1309 annotation.start_col.file + pad,
1310 annotation.end_col.file - pad,
1311 self.margin(),
1312 );
1313 }
1314 }
1315 annotations_position
1316 .iter()
1317 .filter_map(|&(_, annotation)| match annotation.annotation_type {
1318 AnnotationType::MultilineStart(p) | AnnotationType::MultilineEnd(p) => {
1319 let style = if annotation.is_primary {
1320 Style::LabelPrimary
1321 } else {
1322 Style::LabelSecondary
1323 };
1324 Some((p, style))
1325 }
1326 _ => None,
1327 })
1328 .collect::<Vec<_>>()
1329 }
1330
1331 fn get_multispan_max_line_num(&mut self, msp: &MultiSpan) -> usize {
1332 let Some(ref sm) = self.sm else {
1333 return 0;
1334 };
1335
1336 let will_be_emitted = |span: Span| {
1337 !span.is_dummy() && {
1338 let file = sm.lookup_source_file(span.hi());
1339 should_show_source_code(&self.ignored_directories_in_source_blocks, sm, &file)
1340 }
1341 };
1342
1343 let mut max = 0;
1344 for primary_span in msp.primary_spans() {
1345 if will_be_emitted(*primary_span) {
1346 let hi = sm.lookup_char_pos(primary_span.hi());
1347 max = (hi.line).max(max);
1348 }
1349 }
1350 if !self.short_message {
1351 for span_label in msp.span_labels() {
1352 if will_be_emitted(span_label.span) {
1353 let hi = sm.lookup_char_pos(span_label.span.hi());
1354 max = (hi.line).max(max);
1355 }
1356 }
1357 }
1358
1359 max
1360 }
1361
1362 fn get_max_line_num(&mut self, span: &MultiSpan, children: &[Subdiag]) -> usize {
1363 let primary = self.get_multispan_max_line_num(span);
1364 children
1365 .iter()
1366 .map(|sub| self.get_multispan_max_line_num(&sub.span))
1367 .max()
1368 .unwrap_or(0)
1369 .max(primary)
1370 }
1371
1372 fn msgs_to_buffer(
1375 &self,
1376 buffer: &mut StyledBuffer,
1377 msgs: &[(DiagMessage, Style)],
1378 args: &FluentArgs<'_>,
1379 padding: usize,
1380 label: &str,
1381 override_style: Option<Style>,
1382 ) -> usize {
1383 let padding = " ".repeat(padding + label.len() + 5);
1400
1401 fn style_or_override(style: Style, override_: Option<Style>) -> Style {
1403 match (style, override_) {
1404 (Style::NoStyle, Some(override_)) => override_,
1405 _ => style,
1406 }
1407 }
1408
1409 let mut line_number = 0;
1410
1411 for (text, style) in msgs.iter() {
1431 let text = self.translator.translate_message(text, args).map_err(Report::new).unwrap();
1432 let text = &normalize_whitespace(&text);
1433 let lines = text.split('\n').collect::<Vec<_>>();
1434 if lines.len() > 1 {
1435 for (i, line) in lines.iter().enumerate() {
1436 if i != 0 {
1437 line_number += 1;
1438 buffer.append(line_number, &padding, Style::NoStyle);
1439 }
1440 buffer.append(line_number, line, style_or_override(*style, override_style));
1441 }
1442 } else {
1443 buffer.append(line_number, text, style_or_override(*style, override_style));
1444 }
1445 }
1446 line_number
1447 }
1448
1449 #[instrument(level = "trace", skip(self, args), ret)]
1450 fn emit_messages_default_inner(
1451 &mut self,
1452 msp: &MultiSpan,
1453 msgs: &[(DiagMessage, Style)],
1454 args: &FluentArgs<'_>,
1455 code: &Option<ErrCode>,
1456 level: &Level,
1457 max_line_num_len: usize,
1458 is_secondary: bool,
1459 is_cont: bool,
1460 ) -> io::Result<CodeWindowStatus> {
1461 let mut buffer = StyledBuffer::new();
1462
1463 if !msp.has_primary_spans() && !msp.has_span_labels() && is_secondary && !self.short_message
1464 {
1465 for _ in 0..max_line_num_len {
1467 buffer.prepend(0, " ", Style::NoStyle);
1468 }
1469 self.draw_note_separator(&mut buffer, 0, max_line_num_len + 1, is_cont);
1470 if *level != Level::FailureNote {
1471 buffer.append(0, level.to_str(), Style::MainHeaderMsg);
1472 buffer.append(0, ": ", Style::NoStyle);
1473 }
1474 let printed_lines =
1475 self.msgs_to_buffer(&mut buffer, msgs, args, max_line_num_len, "note", None);
1476 if is_cont && matches!(self.theme, OutputTheme::Unicode) {
1477 for i in 1..=printed_lines {
1489 self.draw_col_separator_no_space(&mut buffer, i, max_line_num_len + 1);
1490 }
1491 }
1492 } else {
1493 let mut label_width = 0;
1494 if *level != Level::FailureNote {
1496 buffer.append(0, level.to_str(), Style::Level(*level));
1497 label_width += level.to_str().len();
1498 }
1499 if let Some(code) = code {
1500 buffer.append(0, "[", Style::Level(*level));
1501 let code = if let TerminalUrl::Yes = self.terminal_url {
1502 let path = "https://doc.rust-lang.org/error_codes";
1503 format!("\x1b]8;;{path}/{code}.html\x07{code}\x1b]8;;\x07")
1504 } else {
1505 code.to_string()
1506 };
1507 buffer.append(0, &code, Style::Level(*level));
1508 buffer.append(0, "]", Style::Level(*level));
1509 label_width += 2 + code.len();
1510 }
1511 let header_style = if is_secondary {
1512 Style::HeaderMsg
1513 } else if self.short_message {
1514 Style::NoStyle
1516 } else {
1517 Style::MainHeaderMsg
1518 };
1519 if *level != Level::FailureNote {
1520 buffer.append(0, ": ", header_style);
1521 label_width += 2;
1522 }
1523 let mut line = 0;
1524 let mut pad = false;
1525 for (text, style) in msgs.iter() {
1526 let text =
1527 self.translator.translate_message(text, args).map_err(Report::new).unwrap();
1528 for text in normalize_whitespace(&text).split('\n') {
1530 buffer.append(
1531 line,
1532 &format!(
1533 "{}{}",
1534 if pad { " ".repeat(label_width) } else { String::new() },
1535 text
1536 ),
1537 match style {
1538 Style::Highlight => *style,
1539 _ => header_style,
1540 },
1541 );
1542 line += 1;
1543 pad = true;
1544 }
1545 pad = false;
1546 if line > 0 {
1552 line -= 1;
1553 }
1554 }
1555 if self.short_message {
1556 let labels = msp
1557 .span_labels()
1558 .into_iter()
1559 .filter_map(|label| match label.label {
1560 Some(msg) if label.is_primary => {
1561 let text = self.translator.translate_message(&msg, args).ok()?;
1562 if !text.trim().is_empty() { Some(text.to_string()) } else { None }
1563 }
1564 _ => None,
1565 })
1566 .collect::<Vec<_>>()
1567 .join(", ");
1568 if !labels.is_empty() {
1569 buffer.append(line, ": ", Style::NoStyle);
1570 buffer.append(line, &labels, Style::NoStyle);
1571 }
1572 }
1573 }
1574 let mut annotated_files = FileWithAnnotatedLines::collect_annotations(self, args, msp);
1575 trace!("{annotated_files:#?}");
1576 let mut code_window_status = CodeWindowStatus::Open;
1577
1578 let primary_span = msp.primary_span().unwrap_or_default();
1580 let (Some(sm), false) = (self.sm.as_ref(), primary_span.is_dummy()) else {
1581 return emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)
1583 .map(|_| code_window_status);
1584 };
1585 let primary_lo = sm.lookup_char_pos(primary_span.lo());
1586 if let Ok(pos) =
1587 annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
1588 {
1589 annotated_files.swap(0, pos);
1590 }
1591
1592 let mut col_sep_before_no_show_source = false;
1595 let annotated_files_len = annotated_files.len();
1596 for (file_idx, annotated_file) in annotated_files.into_iter().enumerate() {
1598 if !should_show_source_code(
1600 &self.ignored_directories_in_source_blocks,
1601 sm,
1602 &annotated_file.file,
1603 ) {
1604 if !self.short_message {
1605 if col_sep_before_no_show_source {
1616 let buffer_msg_line_offset = buffer.num_lines();
1617 self.draw_col_separator_end(
1618 &mut buffer,
1619 buffer_msg_line_offset,
1620 max_line_num_len + 1,
1621 );
1622 }
1623 col_sep_before_no_show_source = false;
1624
1625 for (annotation_id, line) in annotated_file.lines.iter().enumerate() {
1627 let mut annotations = line.annotations.clone();
1628 annotations.sort_by_key(|a| Reverse(a.start_col));
1629 let mut line_idx = buffer.num_lines();
1630
1631 let labels: Vec<_> = annotations
1632 .iter()
1633 .filter_map(|a| Some((a.label.as_ref()?, a.is_primary)))
1634 .filter(|(l, _)| !l.is_empty())
1635 .collect();
1636
1637 if annotation_id == 0 || !labels.is_empty() {
1638 buffer.append(
1639 line_idx,
1640 &format!(
1641 "{}:{}:{}",
1642 sm.filename_for_diagnostics(&annotated_file.file.name),
1643 sm.doctest_offset_line(
1644 &annotated_file.file.name,
1645 line.line_index
1646 ),
1647 annotations[0].start_col.file + 1,
1648 ),
1649 Style::LineAndColumn,
1650 );
1651 if annotation_id == 0 {
1652 buffer.prepend(line_idx, self.file_start(), Style::LineNumber);
1653 } else {
1654 buffer.prepend(
1655 line_idx,
1656 self.secondary_file_start(),
1657 Style::LineNumber,
1658 );
1659 }
1660 for _ in 0..max_line_num_len {
1661 buffer.prepend(line_idx, " ", Style::NoStyle);
1662 }
1663 line_idx += 1;
1664 }
1665 if is_cont
1666 && file_idx == annotated_files_len - 1
1667 && annotation_id == annotated_file.lines.len() - 1
1668 && !labels.is_empty()
1669 {
1670 code_window_status = CodeWindowStatus::Closed;
1671 }
1672 let labels_len = labels.len();
1673 for (label_idx, (label, is_primary)) in labels.into_iter().enumerate() {
1674 let style = if is_primary {
1675 Style::LabelPrimary
1676 } else {
1677 Style::LabelSecondary
1678 };
1679 self.draw_col_separator_no_space(
1680 &mut buffer,
1681 line_idx,
1682 max_line_num_len + 1,
1683 );
1684 line_idx += 1;
1685 self.draw_note_separator(
1686 &mut buffer,
1687 line_idx,
1688 max_line_num_len + 1,
1689 label_idx != labels_len - 1,
1690 );
1691 buffer.append(line_idx, "note", Style::MainHeaderMsg);
1692 buffer.append(line_idx, ": ", Style::NoStyle);
1693 buffer.append(line_idx, label, style);
1694 line_idx += 1;
1695 }
1696 }
1697 }
1698 continue;
1699 } else {
1700 col_sep_before_no_show_source = true;
1701 }
1702 let is_primary = primary_lo.file.stable_id == annotated_file.file.stable_id;
1705 if is_primary {
1706 let loc = primary_lo.clone();
1707 if !self.short_message {
1708 let buffer_msg_line_offset = buffer.num_lines();
1710
1711 buffer.prepend(buffer_msg_line_offset, self.file_start(), Style::LineNumber);
1712 buffer.append(
1713 buffer_msg_line_offset,
1714 &format!(
1715 "{}:{}:{}",
1716 sm.filename_for_diagnostics(&loc.file.name),
1717 sm.doctest_offset_line(&loc.file.name, loc.line),
1718 loc.col.0 + 1,
1719 ),
1720 Style::LineAndColumn,
1721 );
1722 for _ in 0..max_line_num_len {
1723 buffer.prepend(buffer_msg_line_offset, " ", Style::NoStyle);
1724 }
1725 } else {
1726 buffer.prepend(
1727 0,
1728 &format!(
1729 "{}:{}:{}: ",
1730 sm.filename_for_diagnostics(&loc.file.name),
1731 sm.doctest_offset_line(&loc.file.name, loc.line),
1732 loc.col.0 + 1,
1733 ),
1734 Style::LineAndColumn,
1735 );
1736 }
1737 } else if !self.short_message {
1738 let buffer_msg_line_offset = buffer.num_lines();
1740
1741 self.draw_col_separator_no_space(
1752 &mut buffer,
1753 buffer_msg_line_offset,
1754 max_line_num_len + 1,
1755 );
1756
1757 buffer.prepend(
1759 buffer_msg_line_offset + 1,
1760 self.secondary_file_start(),
1761 Style::LineNumber,
1762 );
1763 let loc = if let Some(first_line) = annotated_file.lines.first() {
1764 let col = if let Some(first_annotation) = first_line.annotations.first() {
1765 format!(":{}", first_annotation.start_col.file + 1)
1766 } else {
1767 String::new()
1768 };
1769 format!(
1770 "{}:{}{}",
1771 sm.filename_for_diagnostics(&annotated_file.file.name),
1772 sm.doctest_offset_line(&annotated_file.file.name, first_line.line_index),
1773 col
1774 )
1775 } else {
1776 format!("{}", sm.filename_for_diagnostics(&annotated_file.file.name))
1777 };
1778 buffer.append(buffer_msg_line_offset + 1, &loc, Style::LineAndColumn);
1779 for _ in 0..max_line_num_len {
1780 buffer.prepend(buffer_msg_line_offset + 1, " ", Style::NoStyle);
1781 }
1782 }
1783
1784 if !self.short_message {
1785 let buffer_msg_line_offset = buffer.num_lines();
1787 self.draw_col_separator_no_space(
1788 &mut buffer,
1789 buffer_msg_line_offset,
1790 max_line_num_len + 1,
1791 );
1792
1793 let mut multilines = FxIndexMap::default();
1795
1796 let mut whitespace_margin = usize::MAX;
1798 for line_idx in 0..annotated_file.lines.len() {
1799 let file = Arc::clone(&annotated_file.file);
1800 let line = &annotated_file.lines[line_idx];
1801 if let Some(source_string) =
1802 line.line_index.checked_sub(1).and_then(|l| file.get_line(l))
1803 {
1804 let leading_whitespace = source_string
1811 .chars()
1812 .take_while(|c| rustc_lexer::is_whitespace(*c))
1813 .count();
1814 if source_string.chars().any(|c| !rustc_lexer::is_whitespace(c)) {
1815 whitespace_margin = min(whitespace_margin, leading_whitespace);
1816 }
1817 }
1818 }
1819 if whitespace_margin == usize::MAX {
1820 whitespace_margin = 0;
1821 }
1822
1823 let mut span_left_margin = usize::MAX;
1825 for line in &annotated_file.lines {
1826 for ann in &line.annotations {
1827 span_left_margin = min(span_left_margin, ann.start_col.file);
1828 span_left_margin = min(span_left_margin, ann.end_col.file);
1829 }
1830 }
1831 if span_left_margin == usize::MAX {
1832 span_left_margin = 0;
1833 }
1834
1835 let mut span_right_margin = 0;
1837 let mut label_right_margin = 0;
1838 let mut max_line_len = 0;
1839 for line in &annotated_file.lines {
1840 max_line_len = max(
1841 max_line_len,
1842 line.line_index
1843 .checked_sub(1)
1844 .and_then(|l| annotated_file.file.get_line(l))
1845 .map_or(0, |s| s.len()),
1846 );
1847 for ann in &line.annotations {
1848 span_right_margin = max(span_right_margin, ann.start_col.file);
1849 span_right_margin = max(span_right_margin, ann.end_col.file);
1850 let label_right = ann.label.as_ref().map_or(0, |l| l.len() + 1);
1852 label_right_margin =
1853 max(label_right_margin, ann.end_col.file + label_right);
1854 }
1855 }
1856
1857 let width_offset = 3 + max_line_num_len;
1858 let code_offset = if annotated_file.multiline_depth == 0 {
1859 width_offset
1860 } else {
1861 width_offset + annotated_file.multiline_depth + 1
1862 };
1863
1864 let column_width = self.column_width(code_offset);
1865
1866 let margin = Margin::new(
1867 whitespace_margin,
1868 span_left_margin,
1869 span_right_margin,
1870 label_right_margin,
1871 column_width,
1872 max_line_len,
1873 );
1874
1875 for line_idx in 0..annotated_file.lines.len() {
1877 let previous_buffer_line = buffer.num_lines();
1878
1879 let depths = self.render_source_line(
1880 &mut buffer,
1881 Arc::clone(&annotated_file.file),
1882 &annotated_file.lines[line_idx],
1883 width_offset,
1884 code_offset,
1885 margin,
1886 !is_cont
1887 && file_idx + 1 == annotated_files_len
1888 && line_idx + 1 == annotated_file.lines.len(),
1889 );
1890
1891 let mut to_add = FxIndexMap::default();
1892
1893 for (depth, style) in depths {
1894 if multilines.swap_remove(&depth).is_none() {
1896 to_add.insert(depth, style);
1897 }
1898 }
1899
1900 for (depth, style) in &multilines {
1903 for line in previous_buffer_line..buffer.num_lines() {
1904 self.draw_multiline_line(
1905 &mut buffer,
1906 line,
1907 width_offset,
1908 *depth,
1909 *style,
1910 );
1911 }
1912 }
1913 if line_idx < (annotated_file.lines.len() - 1) {
1916 let line_idx_delta = annotated_file.lines[line_idx + 1].line_index
1917 - annotated_file.lines[line_idx].line_index;
1918 if line_idx_delta > 2 {
1919 let last_buffer_line_num = buffer.num_lines();
1920 self.draw_line_separator(
1921 &mut buffer,
1922 last_buffer_line_num,
1923 width_offset,
1924 );
1925
1926 for (depth, style) in &multilines {
1928 self.draw_multiline_line(
1929 &mut buffer,
1930 last_buffer_line_num,
1931 width_offset,
1932 *depth,
1933 *style,
1934 );
1935 }
1936 if let Some(line) = annotated_file.lines.get(line_idx) {
1937 for ann in &line.annotations {
1938 if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1939 {
1940 self.draw_multiline_line(
1944 &mut buffer,
1945 last_buffer_line_num,
1946 width_offset,
1947 pos,
1948 if ann.is_primary {
1949 Style::UnderlinePrimary
1950 } else {
1951 Style::UnderlineSecondary
1952 },
1953 );
1954 }
1955 }
1956 }
1957 } else if line_idx_delta == 2 {
1958 let unannotated_line = annotated_file
1959 .file
1960 .get_line(annotated_file.lines[line_idx].line_index)
1961 .unwrap_or_else(|| Cow::from(""));
1962
1963 let last_buffer_line_num = buffer.num_lines();
1964
1965 self.draw_line(
1966 &mut buffer,
1967 &normalize_whitespace(&unannotated_line),
1968 annotated_file.lines[line_idx + 1].line_index - 1,
1969 last_buffer_line_num,
1970 width_offset,
1971 code_offset,
1972 margin,
1973 );
1974
1975 for (depth, style) in &multilines {
1976 self.draw_multiline_line(
1977 &mut buffer,
1978 last_buffer_line_num,
1979 width_offset,
1980 *depth,
1981 *style,
1982 );
1983 }
1984 if let Some(line) = annotated_file.lines.get(line_idx) {
1985 for ann in &line.annotations {
1986 if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1987 {
1988 self.draw_multiline_line(
1989 &mut buffer,
1990 last_buffer_line_num,
1991 width_offset,
1992 pos,
1993 if ann.is_primary {
1994 Style::UnderlinePrimary
1995 } else {
1996 Style::UnderlineSecondary
1997 },
1998 );
1999 }
2000 }
2001 }
2002 }
2003 }
2004
2005 multilines.extend(&to_add);
2006 }
2007 }
2008 trace!("buffer: {:#?}", buffer.render());
2009 }
2010
2011 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
2013
2014 Ok(code_window_status)
2015 }
2016
2017 fn column_width(&self, code_offset: usize) -> usize {
2018 if let Some(width) = self.diagnostic_width {
2019 width.saturating_sub(code_offset)
2020 } else if self.ui_testing || cfg!(miri) {
2021 DEFAULT_COLUMN_WIDTH.saturating_sub(code_offset)
2022 } else {
2023 termize::dimensions()
2024 .map(|(w, _)| w.saturating_sub(code_offset))
2025 .unwrap_or(DEFAULT_COLUMN_WIDTH)
2026 }
2027 }
2028
2029 fn emit_suggestion_default(
2030 &mut self,
2031 span: &MultiSpan,
2032 suggestion: &CodeSuggestion,
2033 args: &FluentArgs<'_>,
2034 level: &Level,
2035 max_line_num_len: usize,
2036 ) -> io::Result<()> {
2037 let Some(ref sm) = self.sm else {
2038 return Ok(());
2039 };
2040
2041 let suggestions = suggestion.splice_lines(sm);
2043 debug!(?suggestions);
2044
2045 if suggestions.is_empty() {
2046 return Ok(());
2052 }
2053
2054 let mut buffer = StyledBuffer::new();
2055
2056 buffer.append(0, level.to_str(), Style::Level(*level));
2058 buffer.append(0, ": ", Style::HeaderMsg);
2059
2060 let mut msg = vec![(suggestion.msg.to_owned(), Style::NoStyle)];
2061 if let Some(confusion_type) =
2062 suggestions.iter().take(MAX_SUGGESTIONS).find_map(|(_, _, _, confusion_type)| {
2063 if confusion_type.has_confusion() { Some(*confusion_type) } else { None }
2064 })
2065 {
2066 msg.push((confusion_type.label_text().into(), Style::NoStyle));
2067 }
2068 self.msgs_to_buffer(
2069 &mut buffer,
2070 &msg,
2071 args,
2072 max_line_num_len,
2073 "suggestion",
2074 Some(Style::HeaderMsg),
2075 );
2076
2077 let other_suggestions = suggestions.len().saturating_sub(MAX_SUGGESTIONS);
2078
2079 let mut row_num = 2;
2080 for (i, (complete, parts, highlights, _)) in
2081 suggestions.into_iter().enumerate().take(MAX_SUGGESTIONS)
2082 {
2083 debug!(?complete, ?parts, ?highlights);
2084
2085 let has_deletion =
2086 parts.iter().any(|p| p.is_deletion(sm) || p.is_destructive_replacement(sm));
2087 let is_multiline = complete.lines().count() > 1;
2088
2089 if i == 0 {
2090 self.draw_col_separator_start(&mut buffer, row_num - 1, max_line_num_len + 1);
2091 } else {
2092 buffer.puts(
2093 row_num - 1,
2094 max_line_num_len + 1,
2095 self.multi_suggestion_separator(),
2096 Style::LineNumber,
2097 );
2098 }
2099 if let Some(span) = span.primary_span() {
2100 let loc = sm.lookup_char_pos(parts[0].span.lo());
2105 if (span.is_dummy() || loc.file.name != sm.span_to_filename(span))
2106 && loc.file.name.is_real()
2107 {
2108 let arrow = self.file_start();
2111 buffer.puts(row_num - 1, 0, arrow, Style::LineNumber);
2112 let filename = sm.filename_for_diagnostics(&loc.file.name);
2113 let offset = sm.doctest_offset_line(&loc.file.name, loc.line);
2114 let message = format!("{}:{}:{}", filename, offset, loc.col.0 + 1);
2115 if row_num == 2 {
2116 let col = usize::max(max_line_num_len + 1, arrow.len());
2117 buffer.puts(1, col, &message, Style::LineAndColumn);
2118 } else {
2119 buffer.append(row_num - 1, &message, Style::LineAndColumn);
2120 }
2121 for _ in 0..max_line_num_len {
2122 buffer.prepend(row_num - 1, " ", Style::NoStyle);
2123 }
2124 self.draw_col_separator_no_space(&mut buffer, row_num, max_line_num_len + 1);
2125 row_num += 1;
2126 }
2127 }
2128 let show_code_change = if has_deletion && !is_multiline {
2129 DisplaySuggestion::Diff
2130 } else if let [part] = &parts[..]
2131 && part.snippet.ends_with('\n')
2132 && part.snippet.trim() == complete.trim()
2133 {
2134 DisplaySuggestion::Add
2136 } else if (parts.len() != 1 || parts[0].snippet.trim() != complete.trim())
2137 && !is_multiline
2138 {
2139 DisplaySuggestion::Underline
2140 } else {
2141 DisplaySuggestion::None
2142 };
2143
2144 if let DisplaySuggestion::Diff = show_code_change {
2145 row_num += 1;
2146 }
2147
2148 let file_lines = sm
2149 .span_to_lines(parts[0].span)
2150 .expect("span_to_lines failed when emitting suggestion");
2151
2152 assert!(!file_lines.lines.is_empty() || parts[0].span.is_dummy());
2153
2154 let line_start = sm.lookup_char_pos(parts[0].original_span.lo()).line;
2155 let mut lines = complete.lines();
2156 let lines_len = lines.clone().count();
2157 if lines.clone().next().is_none() {
2158 let line_end = sm.lookup_char_pos(parts[0].original_span.hi()).line;
2160 for line in line_start..=line_end {
2161 self.draw_line_num(
2162 &mut buffer,
2163 line,
2164 row_num - 1 + line - line_start,
2165 max_line_num_len,
2166 );
2167 buffer.puts(
2168 row_num - 1 + line - line_start,
2169 max_line_num_len + 1,
2170 "- ",
2171 Style::Removal,
2172 );
2173 buffer.puts(
2174 row_num - 1 + line - line_start,
2175 max_line_num_len + 3,
2176 &normalize_whitespace(&file_lines.file.get_line(line - 1).unwrap()),
2177 Style::Removal,
2178 );
2179 }
2180 row_num += line_end - line_start;
2181 }
2182 let mut unhighlighted_lines = Vec::new();
2183 let mut last_pos = 0;
2184 let mut is_item_attribute = false;
2185 for (line_pos, (line, highlight_parts)) in lines.by_ref().zip(highlights).enumerate() {
2186 last_pos = line_pos;
2187 debug!(%line_pos, %line, ?highlight_parts);
2188
2189 if highlight_parts.is_empty() {
2191 unhighlighted_lines.push((line_pos, line));
2192 continue;
2193 }
2194 if highlight_parts.len() == 1
2195 && line.trim().starts_with("#[")
2196 && line.trim().ends_with(']')
2197 && lines_len == 1
2198 {
2199 is_item_attribute = true;
2200 }
2201
2202 match unhighlighted_lines.len() {
2203 0 => (),
2204 n if n <= 3 => unhighlighted_lines.drain(..).for_each(|(p, l)| {
2209 self.draw_code_line(
2210 &mut buffer,
2211 &mut row_num,
2212 &[],
2213 p + line_start,
2214 l,
2215 show_code_change,
2216 max_line_num_len,
2217 &file_lines,
2218 is_multiline,
2219 )
2220 }),
2221 _ => {
2229 let last_line = unhighlighted_lines.pop();
2230 let first_line = unhighlighted_lines.drain(..).next();
2231
2232 if let Some((p, l)) = first_line {
2233 self.draw_code_line(
2234 &mut buffer,
2235 &mut row_num,
2236 &[],
2237 p + line_start,
2238 l,
2239 show_code_change,
2240 max_line_num_len,
2241 &file_lines,
2242 is_multiline,
2243 )
2244 }
2245
2246 let placeholder = self.margin();
2247 let padding = str_width(placeholder);
2248 buffer.puts(
2249 row_num,
2250 max_line_num_len.saturating_sub(padding),
2251 placeholder,
2252 Style::LineNumber,
2253 );
2254 row_num += 1;
2255
2256 if let Some((p, l)) = last_line {
2257 self.draw_code_line(
2258 &mut buffer,
2259 &mut row_num,
2260 &[],
2261 p + line_start,
2262 l,
2263 show_code_change,
2264 max_line_num_len,
2265 &file_lines,
2266 is_multiline,
2267 )
2268 }
2269 }
2270 }
2271
2272 self.draw_code_line(
2273 &mut buffer,
2274 &mut row_num,
2275 &highlight_parts,
2276 line_pos + line_start,
2277 line,
2278 show_code_change,
2279 max_line_num_len,
2280 &file_lines,
2281 is_multiline,
2282 )
2283 }
2284 if let DisplaySuggestion::Add = show_code_change
2285 && is_item_attribute
2286 {
2287 let file_lines = sm
2294 .span_to_lines(parts[0].span.shrink_to_hi())
2295 .expect("span_to_lines failed when emitting suggestion");
2296 let line_num = sm.lookup_char_pos(parts[0].span.lo()).line;
2297 if let Some(line) = file_lines.file.get_line(line_num - 1) {
2298 let line = normalize_whitespace(&line);
2299 self.draw_code_line(
2300 &mut buffer,
2301 &mut row_num,
2302 &[],
2303 line_num + last_pos + 1,
2304 &line,
2305 DisplaySuggestion::None,
2306 max_line_num_len,
2307 &file_lines,
2308 is_multiline,
2309 )
2310 }
2311 }
2312
2313 let mut offsets: Vec<(usize, isize)> = Vec::new();
2316 if let DisplaySuggestion::Diff | DisplaySuggestion::Underline | DisplaySuggestion::Add =
2319 show_code_change
2320 {
2321 for part in parts {
2322 let span_start_pos = sm.lookup_char_pos(part.span.lo()).col_display;
2323 let span_end_pos = sm.lookup_char_pos(part.span.hi()).col_display;
2324
2325 let is_whitespace_addition = part.snippet.trim().is_empty();
2328
2329 let start = if is_whitespace_addition {
2331 0
2332 } else {
2333 part.snippet.len().saturating_sub(part.snippet.trim_start().len())
2334 };
2335 let sub_len: usize = str_width(if is_whitespace_addition {
2338 &part.snippet
2339 } else {
2340 part.snippet.trim()
2341 });
2342
2343 let offset: isize = offsets
2344 .iter()
2345 .filter_map(
2346 |(start, v)| if span_start_pos < *start { None } else { Some(v) },
2347 )
2348 .sum();
2349 let underline_start = (span_start_pos + start) as isize + offset;
2350 let underline_end = (span_start_pos + start + sub_len) as isize + offset;
2351 assert!(underline_start >= 0 && underline_end >= 0);
2352 let padding: usize = max_line_num_len + 3;
2353 for p in underline_start..underline_end {
2354 if let DisplaySuggestion::Underline = show_code_change
2355 && is_different(sm, &part.snippet, part.span)
2356 {
2357 buffer.putc(
2360 row_num,
2361 (padding as isize + p) as usize,
2362 if part.is_addition(sm) { '+' } else { self.diff() },
2363 Style::Addition,
2364 );
2365 }
2366 }
2367 if let DisplaySuggestion::Diff = show_code_change {
2368 let snippet = sm.span_to_snippet(part.span).unwrap_or_default();
2398 let newlines = snippet.lines().count();
2399 if newlines > 0 && row_num > newlines {
2400 for (i, line) in snippet.lines().enumerate() {
2409 let line = normalize_whitespace(line);
2410 let row = (row_num - 2 - (newlines - i - 1)).max(2);
2411 let start = if i == 0 {
2417 (padding as isize + span_start_pos as isize) as usize
2418 } else {
2419 padding
2420 };
2421 let end = if i == 0 {
2422 (padding as isize
2423 + span_start_pos as isize
2424 + line.len() as isize)
2425 as usize
2426 } else if i == newlines - 1 {
2427 (padding as isize + span_end_pos as isize) as usize
2428 } else {
2429 (padding as isize + line.len() as isize) as usize
2430 };
2431 buffer.set_style_range(row, start, end, Style::Removal, true);
2432 }
2433 } else {
2434 buffer.set_style_range(
2436 row_num - 2,
2437 (padding as isize + span_start_pos as isize) as usize,
2438 (padding as isize + span_end_pos as isize) as usize,
2439 Style::Removal,
2440 true,
2441 );
2442 }
2443 }
2444
2445 let full_sub_len = str_width(&part.snippet) as isize;
2447
2448 let snippet_len = span_end_pos as isize - span_start_pos as isize;
2450 offsets.push((span_end_pos, full_sub_len - snippet_len));
2454 }
2455 row_num += 1;
2456 }
2457
2458 if lines.next().is_some() {
2460 let placeholder = self.margin();
2461 let padding = str_width(placeholder);
2462 buffer.puts(
2463 row_num,
2464 max_line_num_len.saturating_sub(padding),
2465 placeholder,
2466 Style::LineNumber,
2467 );
2468 } else {
2469 let row = match show_code_change {
2470 DisplaySuggestion::Diff
2471 | DisplaySuggestion::Add
2472 | DisplaySuggestion::Underline => row_num - 1,
2473 DisplaySuggestion::None => row_num,
2474 };
2475 if other_suggestions > 0 {
2476 self.draw_col_separator_no_space(&mut buffer, row, max_line_num_len + 1);
2477 } else {
2478 self.draw_col_separator_end(&mut buffer, row, max_line_num_len + 1);
2479 }
2480 row_num = row + 1;
2481 }
2482 }
2483 if other_suggestions > 0 {
2484 self.draw_note_separator(&mut buffer, row_num, max_line_num_len + 1, false);
2485 let msg = format!(
2486 "and {} other candidate{}",
2487 other_suggestions,
2488 pluralize!(other_suggestions)
2489 );
2490 buffer.append(row_num, &msg, Style::NoStyle);
2491 }
2492
2493 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
2494 Ok(())
2495 }
2496
2497 #[instrument(level = "trace", skip(self, args, code, children, suggestions))]
2498 fn emit_messages_default(
2499 &mut self,
2500 level: &Level,
2501 messages: &[(DiagMessage, Style)],
2502 args: &FluentArgs<'_>,
2503 code: &Option<ErrCode>,
2504 span: &MultiSpan,
2505 children: &[Subdiag],
2506 suggestions: &[CodeSuggestion],
2507 ) {
2508 let max_line_num_len = if self.ui_testing {
2509 ANONYMIZED_LINE_NUM.len()
2510 } else {
2511 let n = self.get_max_line_num(span, children);
2512 num_decimal_digits(n)
2513 };
2514
2515 match self.emit_messages_default_inner(
2516 span,
2517 messages,
2518 args,
2519 code,
2520 level,
2521 max_line_num_len,
2522 false,
2523 !children.is_empty()
2524 || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden),
2525 ) {
2526 Ok(code_window_status) => {
2527 if !children.is_empty()
2528 || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden)
2529 {
2530 let mut buffer = StyledBuffer::new();
2531 if !self.short_message {
2532 if let Some(child) = children.iter().next()
2533 && child.span.primary_spans().is_empty()
2534 {
2535 self.draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1);
2537 } else if matches!(code_window_status, CodeWindowStatus::Open) {
2538 self.draw_col_separator_end(&mut buffer, 0, max_line_num_len + 1);
2540 }
2541 }
2542 if let Err(e) = emit_to_destination(
2543 &buffer.render(),
2544 level,
2545 &mut self.dst,
2546 self.short_message,
2547 ) {
2548 panic!("failed to emit error: {e}")
2549 }
2550 }
2551 if !self.short_message {
2552 for (i, child) in children.iter().enumerate() {
2553 assert!(child.level.can_be_subdiag());
2554 let span = &child.span;
2555 let should_close = match children.get(i + 1) {
2557 Some(c) => !c.span.primary_spans().is_empty(),
2558 None => i + 1 == children.len(),
2559 };
2560 if let Err(err) = self.emit_messages_default_inner(
2561 span,
2562 &child.messages,
2563 args,
2564 &None,
2565 &child.level,
2566 max_line_num_len,
2567 true,
2568 !should_close,
2569 ) {
2570 panic!("failed to emit error: {err}");
2571 }
2572 }
2573 for (i, sugg) in suggestions.iter().enumerate() {
2574 match sugg.style {
2575 SuggestionStyle::CompletelyHidden => {
2576 }
2578 SuggestionStyle::HideCodeAlways => {
2579 if let Err(e) = self.emit_messages_default_inner(
2580 &MultiSpan::new(),
2581 &[(sugg.msg.to_owned(), Style::HeaderMsg)],
2582 args,
2583 &None,
2584 &Level::Help,
2585 max_line_num_len,
2586 true,
2587 i + 1 != suggestions.len(),
2590 ) {
2591 panic!("failed to emit error: {e}");
2592 }
2593 }
2594 SuggestionStyle::HideCodeInline
2595 | SuggestionStyle::ShowCode
2596 | SuggestionStyle::ShowAlways => {
2597 if let Err(e) = self.emit_suggestion_default(
2598 span,
2599 sugg,
2600 args,
2601 &Level::Help,
2602 max_line_num_len,
2603 ) {
2604 panic!("failed to emit error: {e}");
2605 }
2606 }
2607 }
2608 }
2609 }
2610 }
2611 Err(e) => panic!("failed to emit error: {e}"),
2612 }
2613
2614 match writeln!(self.dst) {
2615 Err(e) => panic!("failed to emit error: {e}"),
2616 _ => {
2617 if let Err(e) = self.dst.flush() {
2618 panic!("failed to emit error: {e}")
2619 }
2620 }
2621 }
2622 }
2623
2624 fn draw_code_line(
2625 &self,
2626 buffer: &mut StyledBuffer,
2627 row_num: &mut usize,
2628 highlight_parts: &[SubstitutionHighlight],
2629 line_num: usize,
2630 line_to_add: &str,
2631 show_code_change: DisplaySuggestion,
2632 max_line_num_len: usize,
2633 file_lines: &FileLines,
2634 is_multiline: bool,
2635 ) {
2636 if let DisplaySuggestion::Diff = show_code_change {
2637 let lines_to_remove = file_lines.lines.iter().take(file_lines.lines.len() - 1);
2640 for (index, line_to_remove) in lines_to_remove.enumerate() {
2641 self.draw_line_num(buffer, line_num + index, *row_num - 1, max_line_num_len);
2642 buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2643 let line = normalize_whitespace(
2644 &file_lines.file.get_line(line_to_remove.line_index).unwrap(),
2645 );
2646 buffer.puts(*row_num - 1, max_line_num_len + 3, &line, Style::NoStyle);
2647 *row_num += 1;
2648 }
2649 let last_line_index = file_lines.lines[file_lines.lines.len() - 1].line_index;
2656 let last_line = &file_lines.file.get_line(last_line_index).unwrap();
2657 if last_line != line_to_add {
2658 self.draw_line_num(
2659 buffer,
2660 line_num + file_lines.lines.len() - 1,
2661 *row_num - 1,
2662 max_line_num_len,
2663 );
2664 buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2665 buffer.puts(
2666 *row_num - 1,
2667 max_line_num_len + 3,
2668 &normalize_whitespace(last_line),
2669 Style::NoStyle,
2670 );
2671 if !line_to_add.trim().is_empty() {
2672 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2686 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2687 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2688 } else {
2689 *row_num -= 1;
2690 }
2691 } else {
2692 *row_num -= 2;
2693 }
2694 } else if is_multiline {
2695 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2696 match &highlight_parts {
2697 [SubstitutionHighlight { start: 0, end }] if *end == line_to_add.len() => {
2698 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2699 }
2700 [] | [SubstitutionHighlight { start: 0, end: 0 }] => {
2701 self.draw_col_separator_no_space(buffer, *row_num, max_line_num_len + 1);
2702 }
2703 _ => {
2704 let diff = self.diff();
2705 buffer.puts(
2706 *row_num,
2707 max_line_num_len + 1,
2708 &format!("{diff} "),
2709 Style::Addition,
2710 );
2711 }
2712 }
2713 buffer.puts(
2719 *row_num,
2720 max_line_num_len + 3,
2721 &normalize_whitespace(line_to_add),
2722 Style::NoStyle,
2723 );
2724 } else if let DisplaySuggestion::Add = show_code_change {
2725 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2726 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2727 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2728 } else {
2729 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2730 self.draw_col_separator(buffer, *row_num, max_line_num_len + 1);
2731 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2732 }
2733
2734 for &SubstitutionHighlight { start, end } in highlight_parts {
2736 if start != end {
2738 let tabs: usize = line_to_add
2740 .chars()
2741 .take(start)
2742 .map(|ch| match ch {
2743 '\t' => 3,
2744 _ => 0,
2745 })
2746 .sum();
2747 buffer.set_style_range(
2748 *row_num,
2749 max_line_num_len + 3 + start + tabs,
2750 max_line_num_len + 3 + end + tabs,
2751 Style::Addition,
2752 true,
2753 );
2754 }
2755 }
2756 *row_num += 1;
2757 }
2758
2759 fn underline(&self, is_primary: bool) -> UnderlineParts {
2760 match (self.theme, is_primary) {
2785 (OutputTheme::Ascii, true) => UnderlineParts {
2786 style: Style::UnderlinePrimary,
2787 underline: '^',
2788 label_start: '^',
2789 vertical_text_line: '|',
2790 multiline_vertical: '|',
2791 multiline_horizontal: '_',
2792 multiline_whole_line: '/',
2793 multiline_start_down: '^',
2794 bottom_right: '|',
2795 top_left: ' ',
2796 top_right_flat: '^',
2797 bottom_left: '|',
2798 multiline_end_up: '^',
2799 multiline_end_same_line: '^',
2800 multiline_bottom_right_with_text: '|',
2801 },
2802 (OutputTheme::Ascii, false) => UnderlineParts {
2803 style: Style::UnderlineSecondary,
2804 underline: '-',
2805 label_start: '-',
2806 vertical_text_line: '|',
2807 multiline_vertical: '|',
2808 multiline_horizontal: '_',
2809 multiline_whole_line: '/',
2810 multiline_start_down: '-',
2811 bottom_right: '|',
2812 top_left: ' ',
2813 top_right_flat: '-',
2814 bottom_left: '|',
2815 multiline_end_up: '-',
2816 multiline_end_same_line: '-',
2817 multiline_bottom_right_with_text: '|',
2818 },
2819 (OutputTheme::Unicode, true) => UnderlineParts {
2820 style: Style::UnderlinePrimary,
2821 underline: '━',
2822 label_start: '┯',
2823 vertical_text_line: '│',
2824 multiline_vertical: '┃',
2825 multiline_horizontal: '━',
2826 multiline_whole_line: '┏',
2827 multiline_start_down: '╿',
2828 bottom_right: '┙',
2829 top_left: '┏',
2830 top_right_flat: '┛',
2831 bottom_left: '┗',
2832 multiline_end_up: '╿',
2833 multiline_end_same_line: '┛',
2834 multiline_bottom_right_with_text: '┥',
2835 },
2836 (OutputTheme::Unicode, false) => UnderlineParts {
2837 style: Style::UnderlineSecondary,
2838 underline: '─',
2839 label_start: '┬',
2840 vertical_text_line: '│',
2841 multiline_vertical: '│',
2842 multiline_horizontal: '─',
2843 multiline_whole_line: '┌',
2844 multiline_start_down: '│',
2845 bottom_right: '┘',
2846 top_left: '┌',
2847 top_right_flat: '┘',
2848 bottom_left: '└',
2849 multiline_end_up: '│',
2850 multiline_end_same_line: '┘',
2851 multiline_bottom_right_with_text: '┤',
2852 },
2853 }
2854 }
2855
2856 fn col_separator(&self) -> char {
2857 match self.theme {
2858 OutputTheme::Ascii => '|',
2859 OutputTheme::Unicode => '│',
2860 }
2861 }
2862
2863 fn note_separator(&self, is_cont: bool) -> &'static str {
2864 match self.theme {
2865 OutputTheme::Ascii => "= ",
2866 OutputTheme::Unicode if is_cont => "├ ",
2867 OutputTheme::Unicode => "╰ ",
2868 }
2869 }
2870
2871 fn multi_suggestion_separator(&self) -> &'static str {
2872 match self.theme {
2873 OutputTheme::Ascii => "|",
2874 OutputTheme::Unicode => "├╴",
2875 }
2876 }
2877
2878 fn draw_col_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2879 let chr = self.col_separator();
2880 buffer.puts(line, col, &format!("{chr} "), Style::LineNumber);
2881 }
2882
2883 fn draw_col_separator_no_space(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2884 let chr = self.col_separator();
2885 self.draw_col_separator_no_space_with_style(buffer, chr, line, col, Style::LineNumber);
2886 }
2887
2888 fn draw_col_separator_start(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2889 match self.theme {
2890 OutputTheme::Ascii => {
2891 self.draw_col_separator_no_space_with_style(
2892 buffer,
2893 '|',
2894 line,
2895 col,
2896 Style::LineNumber,
2897 );
2898 }
2899 OutputTheme::Unicode => {
2900 self.draw_col_separator_no_space_with_style(
2901 buffer,
2902 '╭',
2903 line,
2904 col,
2905 Style::LineNumber,
2906 );
2907 self.draw_col_separator_no_space_with_style(
2908 buffer,
2909 '╴',
2910 line,
2911 col + 1,
2912 Style::LineNumber,
2913 );
2914 }
2915 }
2916 }
2917
2918 fn draw_col_separator_end(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2919 match self.theme {
2920 OutputTheme::Ascii => {
2921 self.draw_col_separator_no_space_with_style(
2922 buffer,
2923 '|',
2924 line,
2925 col,
2926 Style::LineNumber,
2927 );
2928 }
2929 OutputTheme::Unicode => {
2930 self.draw_col_separator_no_space_with_style(
2931 buffer,
2932 '╰',
2933 line,
2934 col,
2935 Style::LineNumber,
2936 );
2937 self.draw_col_separator_no_space_with_style(
2938 buffer,
2939 '╴',
2940 line,
2941 col + 1,
2942 Style::LineNumber,
2943 );
2944 }
2945 }
2946 }
2947
2948 fn draw_col_separator_no_space_with_style(
2949 &self,
2950 buffer: &mut StyledBuffer,
2951 chr: char,
2952 line: usize,
2953 col: usize,
2954 style: Style,
2955 ) {
2956 buffer.putc(line, col, chr, style);
2957 }
2958
2959 fn draw_range(
2960 &self,
2961 buffer: &mut StyledBuffer,
2962 symbol: char,
2963 line: usize,
2964 col_from: usize,
2965 col_to: usize,
2966 style: Style,
2967 ) {
2968 for col in col_from..col_to {
2969 buffer.putc(line, col, symbol, style);
2970 }
2971 }
2972
2973 fn draw_note_separator(
2974 &self,
2975 buffer: &mut StyledBuffer,
2976 line: usize,
2977 col: usize,
2978 is_cont: bool,
2979 ) {
2980 let chr = self.note_separator(is_cont);
2981 buffer.puts(line, col, chr, Style::LineNumber);
2982 }
2983
2984 fn draw_multiline_line(
2985 &self,
2986 buffer: &mut StyledBuffer,
2987 line: usize,
2988 offset: usize,
2989 depth: usize,
2990 style: Style,
2991 ) {
2992 let chr = match (style, self.theme) {
2993 (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Ascii) => '|',
2994 (_, OutputTheme::Ascii) => '|',
2995 (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Unicode) => '┃',
2996 (_, OutputTheme::Unicode) => '│',
2997 };
2998 buffer.putc(line, offset + depth - 1, chr, style);
2999 }
3000
3001 fn file_start(&self) -> &'static str {
3002 match self.theme {
3003 OutputTheme::Ascii => "--> ",
3004 OutputTheme::Unicode => " ╭▸ ",
3005 }
3006 }
3007
3008 fn secondary_file_start(&self) -> &'static str {
3009 match self.theme {
3010 OutputTheme::Ascii => "::: ",
3011 OutputTheme::Unicode => " ⸬ ",
3012 }
3013 }
3014
3015 fn diff(&self) -> char {
3016 match self.theme {
3017 OutputTheme::Ascii => '~',
3018 OutputTheme::Unicode => '±',
3019 }
3020 }
3021
3022 fn draw_line_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
3023 let (column, dots) = match self.theme {
3024 OutputTheme::Ascii => (0, "..."),
3025 OutputTheme::Unicode => (col - 2, "‡"),
3026 };
3027 buffer.puts(line, column, dots, Style::LineNumber);
3028 }
3029
3030 fn margin(&self) -> &'static str {
3031 match self.theme {
3032 OutputTheme::Ascii => "...",
3033 OutputTheme::Unicode => "…",
3034 }
3035 }
3036
3037 fn draw_line_num(
3038 &self,
3039 buffer: &mut StyledBuffer,
3040 line_num: usize,
3041 line_offset: usize,
3042 max_line_num_len: usize,
3043 ) {
3044 let line_num = self.maybe_anonymized(line_num);
3045 buffer.puts(
3046 line_offset,
3047 max_line_num_len.saturating_sub(str_width(&line_num)),
3048 &line_num,
3049 Style::LineNumber,
3050 );
3051 }
3052}
3053
3054#[derive(Debug, Clone, Copy)]
3055struct UnderlineParts {
3056 style: Style,
3057 underline: char,
3058 label_start: char,
3059 vertical_text_line: char,
3060 multiline_vertical: char,
3061 multiline_horizontal: char,
3062 multiline_whole_line: char,
3063 multiline_start_down: char,
3064 bottom_right: char,
3065 top_left: char,
3066 top_right_flat: char,
3067 bottom_left: char,
3068 multiline_end_up: char,
3069 multiline_end_same_line: char,
3070 multiline_bottom_right_with_text: char,
3071}
3072
3073#[derive(Clone, Copy, Debug)]
3074enum DisplaySuggestion {
3075 Underline,
3076 Diff,
3077 None,
3078 Add,
3079}
3080
3081#[derive(Clone, Copy, Debug)]
3082enum CodeWindowStatus {
3083 Closed,
3084 Open,
3085}
3086
3087impl FileWithAnnotatedLines {
3088 pub(crate) fn collect_annotations(
3091 emitter: &dyn Emitter,
3092 args: &FluentArgs<'_>,
3093 msp: &MultiSpan,
3094 ) -> Vec<FileWithAnnotatedLines> {
3095 fn add_annotation_to_file(
3096 file_vec: &mut Vec<FileWithAnnotatedLines>,
3097 file: Arc<SourceFile>,
3098 line_index: usize,
3099 ann: Annotation,
3100 ) {
3101 for slot in file_vec.iter_mut() {
3102 if slot.file.stable_id == file.stable_id {
3104 for line_slot in &mut slot.lines {
3106 if line_slot.line_index == line_index {
3107 line_slot.annotations.push(ann);
3108 return;
3109 }
3110 }
3111 slot.lines.push(Line { line_index, annotations: vec![ann] });
3113 slot.lines.sort();
3114 return;
3115 }
3116 }
3117 file_vec.push(FileWithAnnotatedLines {
3119 file,
3120 lines: vec![Line { line_index, annotations: vec![ann] }],
3121 multiline_depth: 0,
3122 });
3123 }
3124 let mut output = vec![];
3125 let mut multiline_annotations = vec![];
3126
3127 if let Some(sm) = emitter.source_map() {
3128 for SpanLabel { span, is_primary, label } in msp.span_labels() {
3129 let span = match (span.is_dummy(), msp.primary_span()) {
3132 (_, None) | (false, _) => span,
3133 (true, Some(span)) => span,
3134 };
3135
3136 let lo = sm.lookup_char_pos(span.lo());
3137 let mut hi = sm.lookup_char_pos(span.hi());
3138
3139 if lo.col_display == hi.col_display && lo.line == hi.line {
3146 hi.col_display += 1;
3147 }
3148
3149 let label = label.as_ref().map(|m| {
3150 normalize_whitespace(
3151 &emitter
3152 .translator()
3153 .translate_message(m, args)
3154 .map_err(Report::new)
3155 .unwrap(),
3156 )
3157 });
3158
3159 if lo.line != hi.line {
3160 let ml = MultilineAnnotation {
3161 depth: 1,
3162 line_start: lo.line,
3163 line_end: hi.line,
3164 start_col: AnnotationColumn::from_loc(&lo),
3165 end_col: AnnotationColumn::from_loc(&hi),
3166 is_primary,
3167 label,
3168 overlaps_exactly: false,
3169 };
3170 multiline_annotations.push((lo.file, ml));
3171 } else {
3172 let ann = Annotation {
3173 start_col: AnnotationColumn::from_loc(&lo),
3174 end_col: AnnotationColumn::from_loc(&hi),
3175 is_primary,
3176 label,
3177 annotation_type: AnnotationType::Singleline,
3178 };
3179 add_annotation_to_file(&mut output, lo.file, lo.line, ann);
3180 };
3181 }
3182 }
3183
3184 multiline_annotations.sort_by_key(|(_, ml)| (ml.line_start, usize::MAX - ml.line_end));
3186 for (_, ann) in multiline_annotations.clone() {
3187 for (_, a) in multiline_annotations.iter_mut() {
3188 if !(ann.same_span(a))
3191 && num_overlap(ann.line_start, ann.line_end, a.line_start, a.line_end, true)
3192 {
3193 a.increase_depth();
3194 } else if ann.same_span(a) && &ann != a {
3195 a.overlaps_exactly = true;
3196 } else {
3197 break;
3198 }
3199 }
3200 }
3201
3202 let mut max_depth = 0; for (_, ann) in &multiline_annotations {
3204 max_depth = max(max_depth, ann.depth);
3205 }
3206 for (_, a) in multiline_annotations.iter_mut() {
3208 a.depth = max_depth - a.depth + 1;
3209 }
3210 for (file, ann) in multiline_annotations {
3211 let mut end_ann = ann.as_end();
3212 if !ann.overlaps_exactly {
3213 add_annotation_to_file(
3236 &mut output,
3237 Arc::clone(&file),
3238 ann.line_start,
3239 ann.as_start(),
3240 );
3241 let middle = min(ann.line_start + 4, ann.line_end);
3246 let filter = |s: &str| {
3250 let s = s.trim();
3251 !(s.starts_with("//") && !(s.starts_with("///") || s.starts_with("//!")))
3253 && !["", "{", "}", "(", ")", "[", "]"].contains(&s)
3255 };
3256 let until = (ann.line_start..middle)
3257 .rev()
3258 .filter_map(|line| file.get_line(line - 1).map(|s| (line + 1, s)))
3259 .find(|(_, s)| filter(s))
3260 .map(|(line, _)| line)
3261 .unwrap_or(ann.line_start);
3262 for line in ann.line_start + 1..until {
3263 add_annotation_to_file(&mut output, Arc::clone(&file), line, ann.as_line());
3265 }
3266 let line_end = ann.line_end - 1;
3267 let end_is_empty = file.get_line(line_end - 1).is_some_and(|s| !filter(&s));
3268 if middle < line_end && !end_is_empty {
3269 add_annotation_to_file(&mut output, Arc::clone(&file), line_end, ann.as_line());
3270 }
3271 } else {
3272 end_ann.annotation_type = AnnotationType::Singleline;
3273 }
3274 add_annotation_to_file(&mut output, file, ann.line_end, end_ann);
3275 }
3276 for file_vec in output.iter_mut() {
3277 file_vec.multiline_depth = max_depth;
3278 }
3279 output
3280 }
3281}
3282
3283fn num_decimal_digits(num: usize) -> usize {
3288 #[cfg(target_pointer_width = "64")]
3289 const MAX_DIGITS: usize = 20;
3290
3291 #[cfg(target_pointer_width = "32")]
3292 const MAX_DIGITS: usize = 10;
3293
3294 #[cfg(target_pointer_width = "16")]
3295 const MAX_DIGITS: usize = 5;
3296
3297 let mut lim = 10;
3298 for num_digits in 1..MAX_DIGITS {
3299 if num < lim {
3300 return num_digits;
3301 }
3302 lim = lim.wrapping_mul(10);
3303 }
3304 MAX_DIGITS
3305}
3306
3307const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
3310 ('\0', "␀"),
3314 ('\u{0001}', "␁"),
3315 ('\u{0002}', "␂"),
3316 ('\u{0003}', "␃"),
3317 ('\u{0004}', "␄"),
3318 ('\u{0005}', "␅"),
3319 ('\u{0006}', "␆"),
3320 ('\u{0007}', "␇"),
3321 ('\u{0008}', "␈"),
3322 ('\t', " "), ('\u{000b}', "␋"),
3324 ('\u{000c}', "␌"),
3325 ('\u{000d}', "␍"),
3326 ('\u{000e}', "␎"),
3327 ('\u{000f}', "␏"),
3328 ('\u{0010}', "␐"),
3329 ('\u{0011}', "␑"),
3330 ('\u{0012}', "␒"),
3331 ('\u{0013}', "␓"),
3332 ('\u{0014}', "␔"),
3333 ('\u{0015}', "␕"),
3334 ('\u{0016}', "␖"),
3335 ('\u{0017}', "␗"),
3336 ('\u{0018}', "␘"),
3337 ('\u{0019}', "␙"),
3338 ('\u{001a}', "␚"),
3339 ('\u{001b}', "␛"),
3340 ('\u{001c}', "␜"),
3341 ('\u{001d}', "␝"),
3342 ('\u{001e}', "␞"),
3343 ('\u{001f}', "␟"),
3344 ('\u{007f}', "␡"),
3345 ('\u{200d}', ""), ('\u{202a}', "�"), ('\u{202b}', "�"), ('\u{202c}', "�"), ('\u{202d}', "�"),
3350 ('\u{202e}', "�"),
3351 ('\u{2066}', "�"),
3352 ('\u{2067}', "�"),
3353 ('\u{2068}', "�"),
3354 ('\u{2069}', "�"),
3355];
3356
3357pub(crate) fn normalize_whitespace(s: &str) -> String {
3358 const {
3359 let mut i = 1;
3360 while i < OUTPUT_REPLACEMENTS.len() {
3361 assert!(
3362 OUTPUT_REPLACEMENTS[i - 1].0 < OUTPUT_REPLACEMENTS[i].0,
3363 "The OUTPUT_REPLACEMENTS array must be sorted (for binary search to work) \
3364 and must contain no duplicate entries"
3365 );
3366 i += 1;
3367 }
3368 }
3369 s.chars().fold(String::with_capacity(s.len()), |mut s, c| {
3373 match OUTPUT_REPLACEMENTS.binary_search_by_key(&c, |(k, _)| *k) {
3374 Ok(i) => s.push_str(OUTPUT_REPLACEMENTS[i].1),
3375 _ => s.push(c),
3376 }
3377 s
3378 })
3379}
3380
3381fn num_overlap(
3382 a_start: usize,
3383 a_end: usize,
3384 b_start: usize,
3385 b_end: usize,
3386 inclusive: bool,
3387) -> bool {
3388 let extra = if inclusive { 1 } else { 0 };
3389 (b_start..b_end + extra).contains(&a_start) || (a_start..a_end + extra).contains(&b_start)
3390}
3391
3392fn overlaps(a1: &Annotation, a2: &Annotation, padding: usize) -> bool {
3393 num_overlap(
3394 a1.start_col.display,
3395 a1.end_col.display + padding,
3396 a2.start_col.display,
3397 a2.end_col.display,
3398 false,
3399 )
3400}
3401
3402pub(crate) fn emit_to_destination(
3403 rendered_buffer: &[Vec<StyledString>],
3404 lvl: &Level,
3405 dst: &mut Destination,
3406 short_message: bool,
3407) -> io::Result<()> {
3408 use crate::lock;
3409
3410 let _buffer_lock = lock::acquire_global_lock("rustc_errors");
3423 for (pos, line) in rendered_buffer.iter().enumerate() {
3424 for part in line {
3425 let style = part.style.anstyle(*lvl);
3426 write!(dst, "{style}{}{style:#}", part.text)?;
3427 }
3428 if !short_message && (!lvl.is_failure_note() || pos != rendered_buffer.len() - 1) {
3429 writeln!(dst)?;
3430 }
3431 }
3432 dst.flush()?;
3433 Ok(())
3434}
3435
3436pub type Destination = AutoStream<Box<dyn Write + Send>>;
3437
3438struct Buffy {
3439 buffer_writer: std::io::Stderr,
3440 buffer: Vec<u8>,
3441}
3442
3443impl Write for Buffy {
3444 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
3445 self.buffer.write(buf)
3446 }
3447
3448 fn flush(&mut self) -> io::Result<()> {
3449 self.buffer_writer.write_all(&self.buffer)?;
3450 self.buffer.clear();
3451 Ok(())
3452 }
3453}
3454
3455impl Drop for Buffy {
3456 fn drop(&mut self) {
3457 if !self.buffer.is_empty() {
3458 self.flush().unwrap();
3459 panic!("buffers need to be flushed in order to print their contents");
3460 }
3461 }
3462}
3463
3464pub fn stderr_destination(color: ColorConfig) -> Destination {
3465 let buffer_writer = std::io::stderr();
3466 let choice = get_stderr_color_choice(color, &buffer_writer);
3469 if cfg!(windows) {
3476 AutoStream::new(Box::new(buffer_writer), choice)
3477 } else {
3478 let buffer = Vec::new();
3479 AutoStream::new(Box::new(Buffy { buffer_writer, buffer }), choice)
3480 }
3481}
3482
3483pub fn get_stderr_color_choice(color: ColorConfig, stderr: &std::io::Stderr) -> ColorChoice {
3484 let choice = color.to_color_choice();
3485 if matches!(choice, ColorChoice::Auto) { AutoStream::choice(stderr) } else { choice }
3486}
3487
3488const BRIGHT_BLUE: anstyle::Style = if cfg!(windows) {
3492 AnsiColor::BrightCyan.on_default()
3493} else {
3494 AnsiColor::BrightBlue.on_default()
3495};
3496
3497impl Style {
3498 pub(crate) fn anstyle(&self, lvl: Level) -> anstyle::Style {
3499 match self {
3500 Style::Addition => AnsiColor::BrightGreen.on_default(),
3501 Style::Removal => AnsiColor::BrightRed.on_default(),
3502 Style::LineAndColumn => anstyle::Style::new(),
3503 Style::LineNumber => BRIGHT_BLUE.effects(Effects::BOLD),
3504 Style::Quotation => anstyle::Style::new(),
3505 Style::MainHeaderMsg => if cfg!(windows) {
3506 AnsiColor::BrightWhite.on_default()
3507 } else {
3508 anstyle::Style::new()
3509 }
3510 .effects(Effects::BOLD),
3511 Style::UnderlinePrimary | Style::LabelPrimary => lvl.color().effects(Effects::BOLD),
3512 Style::UnderlineSecondary | Style::LabelSecondary => BRIGHT_BLUE.effects(Effects::BOLD),
3513 Style::HeaderMsg | Style::NoStyle => anstyle::Style::new(),
3514 Style::Level(lvl) => lvl.color().effects(Effects::BOLD),
3515 Style::Highlight => AnsiColor::Magenta.on_default().effects(Effects::BOLD),
3516 }
3517 }
3518}
3519
3520pub fn is_different(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
3522 let found = match sm.span_to_snippet(sp) {
3523 Ok(snippet) => snippet,
3524 Err(e) => {
3525 warn!(error = ?e, "Invalid span {:?}", sp);
3526 return true;
3527 }
3528 };
3529 found != suggested
3530}
3531
3532pub fn detect_confusion_type(sm: &SourceMap, suggested: &str, sp: Span) -> ConfusionType {
3534 let found = match sm.span_to_snippet(sp) {
3535 Ok(snippet) => snippet,
3536 Err(e) => {
3537 warn!(error = ?e, "Invalid span {:?}", sp);
3538 return ConfusionType::None;
3539 }
3540 };
3541
3542 let mut has_case_confusion = false;
3543 let mut has_digit_letter_confusion = false;
3544
3545 if found.len() == suggested.len() {
3546 let mut has_case_diff = false;
3547 let mut has_digit_letter_confusable = false;
3548 let mut has_other_diff = false;
3549
3550 let ascii_confusables = &['c', 'f', 'i', 'k', 'o', 's', 'u', 'v', 'w', 'x', 'y', 'z'];
3553
3554 let digit_letter_confusables = [('0', 'O'), ('1', 'l'), ('5', 'S'), ('8', 'B'), ('9', 'g')];
3555
3556 for (f, s) in iter::zip(found.chars(), suggested.chars()) {
3557 if f != s {
3558 if f.eq_ignore_ascii_case(&s) {
3559 if ascii_confusables.contains(&f) || ascii_confusables.contains(&s) {
3561 has_case_diff = true;
3562 } else {
3563 has_other_diff = true;
3564 }
3565 } else if digit_letter_confusables.contains(&(f, s))
3566 || digit_letter_confusables.contains(&(s, f))
3567 {
3568 has_digit_letter_confusable = true;
3570 } else {
3571 has_other_diff = true;
3572 }
3573 }
3574 }
3575
3576 if has_case_diff && !has_other_diff && found != suggested {
3578 has_case_confusion = true;
3579 }
3580 if has_digit_letter_confusable && !has_other_diff && found != suggested {
3581 has_digit_letter_confusion = true;
3582 }
3583 }
3584
3585 match (has_case_confusion, has_digit_letter_confusion) {
3586 (true, true) => ConfusionType::Both,
3587 (true, false) => ConfusionType::Case,
3588 (false, true) => ConfusionType::DigitLetter,
3589 (false, false) => ConfusionType::None,
3590 }
3591}
3592
3593#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3595pub enum ConfusionType {
3596 None,
3598 Case,
3600 DigitLetter,
3602 Both,
3604}
3605
3606impl ConfusionType {
3607 pub fn label_text(&self) -> &'static str {
3609 match self {
3610 ConfusionType::None => "",
3611 ConfusionType::Case => " (notice the capitalization)",
3612 ConfusionType::DigitLetter => " (notice the digit/letter confusion)",
3613 ConfusionType::Both => " (notice the capitalization and digit/letter confusion)",
3614 }
3615 }
3616
3617 pub fn combine(self, other: ConfusionType) -> ConfusionType {
3621 match (self, other) {
3622 (ConfusionType::None, other) => other,
3623 (this, ConfusionType::None) => this,
3624 (ConfusionType::Both, _) | (_, ConfusionType::Both) => ConfusionType::Both,
3625 (ConfusionType::Case, ConfusionType::DigitLetter)
3626 | (ConfusionType::DigitLetter, ConfusionType::Case) => ConfusionType::Both,
3627 (ConfusionType::Case, ConfusionType::Case) => ConfusionType::Case,
3628 (ConfusionType::DigitLetter, ConfusionType::DigitLetter) => ConfusionType::DigitLetter,
3629 }
3630 }
3631
3632 pub fn has_confusion(&self) -> bool {
3634 *self != ConfusionType::None
3635 }
3636}
3637
3638pub(crate) fn should_show_source_code(
3639 ignored_directories: &[String],
3640 sm: &SourceMap,
3641 file: &SourceFile,
3642) -> bool {
3643 if !sm.ensure_source_file_source_present(file) {
3644 return false;
3645 }
3646
3647 let FileName::Real(name) = &file.name else { return true };
3648 name.local_path()
3649 .map(|path| ignored_directories.iter().all(|dir| !path.starts_with(dir)))
3650 .unwrap_or(true)
3651}