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 derive_setters::Setters;
20use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
21use rustc_data_structures::sync::{DynSend, IntoDynSyncSend};
22use rustc_error_messages::{FluentArgs, SpanLabel};
23use rustc_lexer;
24use rustc_lint_defs::pluralize;
25use rustc_span::hygiene::{ExpnKind, MacroKind};
26use rustc_span::source_map::SourceMap;
27use rustc_span::{FileLines, FileName, SourceFile, Span, char_width, str_width};
28use termcolor::{Buffer, BufferWriter, Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
29use tracing::{debug, instrument, trace, warn};
30
31use crate::registry::Registry;
32use crate::snippet::{
33 Annotation, AnnotationColumn, AnnotationType, Line, MultilineAnnotation, Style, StyledString,
34};
35use crate::styled_buffer::StyledBuffer;
36use crate::timings::TimingRecord;
37use crate::translation::{Translator, to_fluent_args};
38use crate::{
39 CodeSuggestion, DiagInner, DiagMessage, ErrCode, Level, MultiSpan, Subdiag,
40 SubstitutionHighlight, SuggestionStyle, TerminalUrl,
41};
42
43const DEFAULT_COLUMN_WIDTH: usize = 140;
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub enum HumanReadableErrorType {
49 Default,
50 Unicode,
51 AnnotateSnippet,
52 Short,
53}
54
55impl HumanReadableErrorType {
56 pub fn short(&self) -> bool {
57 *self == HumanReadableErrorType::Short
58 }
59}
60
61#[derive(Clone, Copy, Debug)]
62struct Margin {
63 pub whitespace_left: usize,
65 pub span_left: usize,
67 pub span_right: usize,
69 pub computed_left: usize,
71 pub computed_right: usize,
73 pub column_width: usize,
76 pub label_right: usize,
79}
80
81impl Margin {
82 fn new(
83 whitespace_left: usize,
84 span_left: usize,
85 span_right: usize,
86 label_right: usize,
87 column_width: usize,
88 max_line_len: usize,
89 ) -> Self {
90 let mut m = Margin {
100 whitespace_left: whitespace_left.saturating_sub(6),
101 span_left: span_left.saturating_sub(6),
102 span_right: span_right + 6,
103 computed_left: 0,
104 computed_right: 0,
105 column_width,
106 label_right: label_right + 6,
107 };
108 m.compute(max_line_len);
109 m
110 }
111
112 fn was_cut_left(&self) -> bool {
113 self.computed_left > 0
114 }
115
116 fn compute(&mut self, max_line_len: usize) {
117 self.computed_left = if self.whitespace_left > 20 {
122 self.whitespace_left - 16 } else {
124 0
125 };
126 self.computed_right = max(max_line_len, self.computed_left);
129
130 if self.computed_right - self.computed_left > self.column_width {
131 if self.label_right - self.whitespace_left <= self.column_width {
133 self.computed_left = self.whitespace_left;
135 self.computed_right = self.computed_left + self.column_width;
136 } else if self.label_right - self.span_left <= self.column_width {
137 let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2;
139 self.computed_left = self.span_left.saturating_sub(padding_left);
140 self.computed_right = self.computed_left + self.column_width;
141 } else if self.span_right - self.span_left <= self.column_width {
142 let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2;
144 self.computed_left = self.span_left.saturating_sub(padding_left);
145 self.computed_right = self.computed_left + self.column_width;
146 } else {
147 self.computed_left = self.span_left;
149 self.computed_right = self.span_right;
150 }
151 }
152 }
153
154 fn left(&self, line_len: usize) -> usize {
155 min(self.computed_left, line_len)
156 }
157
158 fn right(&self, line_len: usize) -> usize {
159 if line_len.saturating_sub(self.computed_left) <= self.column_width {
160 line_len
161 } else {
162 min(line_len, self.computed_right)
163 }
164 }
165}
166
167pub enum TimingEvent {
168 Start,
169 End,
170}
171
172const ANONYMIZED_LINE_NUM: &str = "LL";
173
174pub type DynEmitter = dyn Emitter + DynSend;
175
176pub trait Emitter {
178 fn emit_diagnostic(&mut self, diag: DiagInner, registry: &Registry);
180
181 fn emit_artifact_notification(&mut self, _path: &Path, _artifact_type: &str) {}
184
185 fn emit_timing_section(&mut self, _record: TimingRecord, _event: TimingEvent) {}
188
189 fn emit_future_breakage_report(&mut self, _diags: Vec<DiagInner>, _registry: &Registry) {}
192
193 fn emit_unused_externs(
196 &mut self,
197 _lint_level: rustc_lint_defs::Level,
198 _unused_externs: &[&str],
199 ) {
200 }
201
202 fn should_show_explain(&self) -> bool {
204 true
205 }
206
207 fn supports_color(&self) -> bool {
209 false
210 }
211
212 fn source_map(&self) -> Option<&SourceMap>;
213
214 fn translator(&self) -> &Translator;
215
216 fn primary_span_formatted(
228 &self,
229 primary_span: &mut MultiSpan,
230 suggestions: &mut Vec<CodeSuggestion>,
231 fluent_args: &FluentArgs<'_>,
232 ) {
233 if let Some((sugg, rest)) = suggestions.split_first() {
234 let msg = self
235 .translator()
236 .translate_message(&sugg.msg, fluent_args)
237 .map_err(Report::new)
238 .unwrap();
239 if rest.is_empty()
240 && let [substitution] = sugg.substitutions.as_slice()
243 && let [part] = substitution.parts.as_slice()
245 && msg.split_whitespace().count() < 10
247 && !part.snippet.contains('\n')
249 && ![
250 SuggestionStyle::HideCodeAlways,
252 SuggestionStyle::CompletelyHidden,
254 SuggestionStyle::ShowAlways,
256 ].contains(&sugg.style)
257 {
258 let snippet = part.snippet.trim();
259 let msg = if snippet.is_empty() || sugg.style.hide_inline() {
260 format!("help: {msg}")
263 } else {
264 let confusion_type = self
266 .source_map()
267 .map(|sm| detect_confusion_type(sm, snippet, part.span))
268 .unwrap_or(ConfusionType::None);
269 format!("help: {}{}: `{}`", msg, confusion_type.label_text(), snippet,)
270 };
271 primary_span.push_span_label(part.span, msg);
272
273 suggestions.clear();
275 } else {
276 }
281 } else {
282 }
284 }
285
286 fn fix_multispans_in_extern_macros_and_render_macro_backtrace(
287 &self,
288 span: &mut MultiSpan,
289 children: &mut Vec<Subdiag>,
290 level: &Level,
291 backtrace: bool,
292 ) {
293 let has_macro_spans: Vec<_> = iter::once(&*span)
296 .chain(children.iter().map(|child| &child.span))
297 .flat_map(|span| span.primary_spans())
298 .flat_map(|sp| sp.macro_backtrace())
299 .filter_map(|expn_data| {
300 match expn_data.kind {
301 ExpnKind::Root => None,
302
303 ExpnKind::Desugaring(..) | ExpnKind::AstPass(..) => None,
306
307 ExpnKind::Macro(macro_kind, name) => {
308 Some((macro_kind, name, expn_data.hide_backtrace))
309 }
310 }
311 })
312 .collect();
313
314 if !backtrace {
315 self.fix_multispans_in_extern_macros(span, children);
316 }
317
318 self.render_multispans_macro_backtrace(span, children, backtrace);
319
320 if !backtrace {
321 if let Some((macro_kind, name, _)) = has_macro_spans.first()
324 && let Some((_, _, false)) = has_macro_spans.last()
325 {
326 let and_then = if let Some((macro_kind, last_name, _)) = has_macro_spans.last()
328 && last_name != name
329 {
330 let descr = macro_kind.descr();
331 format!(" which comes from the expansion of the {descr} `{last_name}`")
332 } else {
333 "".to_string()
334 };
335
336 let descr = macro_kind.descr();
337 let msg = format!(
338 "this {level} originates in the {descr} `{name}`{and_then} \
339 (in Nightly builds, run with -Z macro-backtrace for more info)",
340 );
341
342 children.push(Subdiag {
343 level: Level::Note,
344 messages: vec![(DiagMessage::from(msg), Style::NoStyle)],
345 span: MultiSpan::new(),
346 });
347 }
348 }
349 }
350
351 fn render_multispans_macro_backtrace(
352 &self,
353 span: &mut MultiSpan,
354 children: &mut Vec<Subdiag>,
355 backtrace: bool,
356 ) {
357 for span in iter::once(span).chain(children.iter_mut().map(|child| &mut child.span)) {
358 self.render_multispan_macro_backtrace(span, backtrace);
359 }
360 }
361
362 fn render_multispan_macro_backtrace(&self, span: &mut MultiSpan, always_backtrace: bool) {
363 let mut new_labels = FxIndexSet::default();
364
365 for &sp in span.primary_spans() {
366 if sp.is_dummy() {
367 continue;
368 }
369
370 let macro_backtrace: Vec<_> = sp.macro_backtrace().collect();
374 for (i, trace) in macro_backtrace.iter().rev().enumerate() {
375 if trace.def_site.is_dummy() {
376 continue;
377 }
378
379 if always_backtrace {
380 new_labels.insert((
381 trace.def_site,
382 format!(
383 "in this expansion of `{}`{}",
384 trace.kind.descr(),
385 if macro_backtrace.len() > 1 {
386 format!(" (#{})", i + 1)
389 } else {
390 String::new()
391 },
392 ),
393 ));
394 }
395
396 let redundant_span = trace.call_site.contains(sp);
408
409 if !redundant_span || always_backtrace {
410 let msg: Cow<'static, _> = match trace.kind {
411 ExpnKind::Macro(MacroKind::Attr, _) => {
412 "this attribute macro expansion".into()
413 }
414 ExpnKind::Macro(MacroKind::Derive, _) => {
415 "this derive macro expansion".into()
416 }
417 ExpnKind::Macro(MacroKind::Bang, _) => "this macro invocation".into(),
418 ExpnKind::Root => "the crate root".into(),
419 ExpnKind::AstPass(kind) => kind.descr().into(),
420 ExpnKind::Desugaring(kind) => {
421 format!("this {} desugaring", kind.descr()).into()
422 }
423 };
424 new_labels.insert((
425 trace.call_site,
426 format!(
427 "in {}{}",
428 msg,
429 if macro_backtrace.len() > 1 && always_backtrace {
430 format!(" (#{})", i + 1)
433 } else {
434 String::new()
435 },
436 ),
437 ));
438 }
439 if !always_backtrace {
440 break;
441 }
442 }
443 }
444
445 for (label_span, label_text) in new_labels {
446 span.push_span_label(label_span, label_text);
447 }
448 }
449
450 fn fix_multispans_in_extern_macros(&self, span: &mut MultiSpan, children: &mut Vec<Subdiag>) {
454 debug!("fix_multispans_in_extern_macros: before: span={:?} children={:?}", span, children);
455 self.fix_multispan_in_extern_macros(span);
456 for child in children.iter_mut() {
457 self.fix_multispan_in_extern_macros(&mut child.span);
458 }
459 debug!("fix_multispans_in_extern_macros: after: span={:?} children={:?}", span, children);
460 }
461
462 fn fix_multispan_in_extern_macros(&self, span: &mut MultiSpan) {
466 let Some(source_map) = self.source_map() else { return };
467 let replacements: Vec<(Span, Span)> = span
469 .primary_spans()
470 .iter()
471 .copied()
472 .chain(span.span_labels().iter().map(|sp_label| sp_label.span))
473 .filter_map(|sp| {
474 if !sp.is_dummy() && source_map.is_imported(sp) {
475 let maybe_callsite = sp.source_callsite();
476 if sp != maybe_callsite {
477 return Some((sp, maybe_callsite));
478 }
479 }
480 None
481 })
482 .collect();
483
484 for (from, to) in replacements {
486 span.replace(from, to);
487 }
488 }
489}
490
491impl Emitter for HumanEmitter {
492 fn source_map(&self) -> Option<&SourceMap> {
493 self.sm.as_deref()
494 }
495
496 fn emit_diagnostic(&mut self, mut diag: DiagInner, _registry: &Registry) {
497 let fluent_args = to_fluent_args(diag.args.iter());
498
499 if self.track_diagnostics && diag.span.has_primary_spans() && !diag.span.is_dummy() {
500 diag.children.insert(0, diag.emitted_at_sub_diag());
501 }
502
503 let mut suggestions = diag.suggestions.unwrap_tag();
504 self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args);
505
506 self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
507 &mut diag.span,
508 &mut diag.children,
509 &diag.level,
510 self.macro_backtrace,
511 );
512
513 self.emit_messages_default(
514 &diag.level,
515 &diag.messages,
516 &fluent_args,
517 &diag.code,
518 &diag.span,
519 &diag.children,
520 &suggestions,
521 );
522 }
523
524 fn should_show_explain(&self) -> bool {
525 !self.short_message
526 }
527
528 fn supports_color(&self) -> bool {
529 self.dst.supports_color()
530 }
531
532 fn translator(&self) -> &Translator {
533 &self.translator
534 }
535}
536
537pub struct FatalOnlyEmitter {
541 pub fatal_emitter: Box<dyn Emitter + DynSend>,
542 pub fatal_note: Option<String>,
543}
544
545impl Emitter for FatalOnlyEmitter {
546 fn source_map(&self) -> Option<&SourceMap> {
547 None
548 }
549
550 fn emit_diagnostic(&mut self, mut diag: DiagInner, registry: &Registry) {
551 if diag.level == Level::Fatal {
552 if let Some(fatal_note) = &self.fatal_note {
553 diag.sub(Level::Note, fatal_note.clone(), MultiSpan::new());
554 }
555 self.fatal_emitter.emit_diagnostic(diag, registry);
556 }
557 }
558
559 fn translator(&self) -> &Translator {
560 self.fatal_emitter.translator()
561 }
562}
563
564pub struct SilentEmitter {
565 pub translator: Translator,
566}
567
568impl Emitter for SilentEmitter {
569 fn source_map(&self) -> Option<&SourceMap> {
570 None
571 }
572
573 fn emit_diagnostic(&mut self, _diag: DiagInner, _registry: &Registry) {}
574
575 fn translator(&self) -> &Translator {
576 &self.translator
577 }
578}
579
580pub const MAX_SUGGESTIONS: usize = 4;
584
585#[derive(Clone, Copy, Debug, PartialEq, Eq)]
586pub enum ColorConfig {
587 Auto,
588 Always,
589 Never,
590}
591
592impl ColorConfig {
593 pub fn to_color_choice(self) -> ColorChoice {
594 match self {
595 ColorConfig::Always => {
596 if io::stderr().is_terminal() {
597 ColorChoice::Always
598 } else {
599 ColorChoice::AlwaysAnsi
600 }
601 }
602 ColorConfig::Never => ColorChoice::Never,
603 ColorConfig::Auto if io::stderr().is_terminal() => ColorChoice::Auto,
604 ColorConfig::Auto => ColorChoice::Never,
605 }
606 }
607}
608
609#[derive(Debug, Clone, Copy, PartialEq, Eq)]
610pub enum OutputTheme {
611 Ascii,
612 Unicode,
613}
614
615#[derive(Setters)]
617pub struct HumanEmitter {
618 #[setters(skip)]
619 dst: IntoDynSyncSend<Destination>,
620 sm: Option<Arc<SourceMap>>,
621 #[setters(skip)]
622 translator: Translator,
623 short_message: bool,
624 ui_testing: bool,
625 ignored_directories_in_source_blocks: Vec<String>,
626 diagnostic_width: Option<usize>,
627
628 macro_backtrace: bool,
629 track_diagnostics: bool,
630 terminal_url: TerminalUrl,
631 theme: OutputTheme,
632}
633
634#[derive(Debug)]
635pub(crate) struct FileWithAnnotatedLines {
636 pub(crate) file: Arc<SourceFile>,
637 pub(crate) lines: Vec<Line>,
638 multiline_depth: usize,
639}
640
641impl HumanEmitter {
642 pub fn new(dst: Destination, translator: Translator) -> HumanEmitter {
643 HumanEmitter {
644 dst: IntoDynSyncSend(dst),
645 sm: None,
646 translator,
647 short_message: false,
648 ui_testing: false,
649 ignored_directories_in_source_blocks: Vec::new(),
650 diagnostic_width: None,
651 macro_backtrace: false,
652 track_diagnostics: false,
653 terminal_url: TerminalUrl::No,
654 theme: OutputTheme::Ascii,
655 }
656 }
657
658 fn maybe_anonymized(&self, line_num: usize) -> Cow<'static, str> {
659 if self.ui_testing {
660 Cow::Borrowed(ANONYMIZED_LINE_NUM)
661 } else {
662 Cow::Owned(line_num.to_string())
663 }
664 }
665
666 fn draw_line(
667 &self,
668 buffer: &mut StyledBuffer,
669 source_string: &str,
670 line_index: usize,
671 line_offset: usize,
672 width_offset: usize,
673 code_offset: usize,
674 margin: Margin,
675 ) -> usize {
676 let line_len = source_string.len();
677 let left = margin.left(line_len);
679 let right = margin.right(line_len);
680 let code: String = source_string
683 .chars()
684 .enumerate()
685 .skip_while(|(i, _)| *i < left)
686 .take_while(|(i, _)| *i < right)
687 .map(|(_, c)| c)
688 .collect();
689 let code = normalize_whitespace(&code);
690 let was_cut_right =
691 source_string.chars().enumerate().skip_while(|(i, _)| *i < right).next().is_some();
692 buffer.puts(line_offset, code_offset, &code, Style::Quotation);
693 let placeholder = self.margin();
694 if margin.was_cut_left() {
695 buffer.puts(line_offset, code_offset, placeholder, Style::LineNumber);
697 }
698 if was_cut_right {
699 let padding = str_width(placeholder);
700 buffer.puts(
702 line_offset,
703 code_offset + str_width(&code) - padding,
704 placeholder,
705 Style::LineNumber,
706 );
707 }
708 self.draw_line_num(buffer, line_index, line_offset, width_offset - 3);
709 self.draw_col_separator_no_space(buffer, line_offset, width_offset - 2);
710 left
711 }
712
713 #[instrument(level = "trace", skip(self), ret)]
714 fn render_source_line(
715 &self,
716 buffer: &mut StyledBuffer,
717 file: Arc<SourceFile>,
718 line: &Line,
719 width_offset: usize,
720 code_offset: usize,
721 margin: Margin,
722 close_window: bool,
723 ) -> Vec<(usize, Style)> {
724 if line.line_index == 0 {
739 return Vec::new();
740 }
741
742 let Some(source_string) = file.get_line(line.line_index - 1) else {
743 return Vec::new();
744 };
745 trace!(?source_string);
746
747 let line_offset = buffer.num_lines();
748
749 let left = self.draw_line(
752 buffer,
753 &source_string,
754 line.line_index,
755 line_offset,
756 width_offset,
757 code_offset,
758 margin,
759 );
760
761 let mut buffer_ops = vec![];
778 let mut annotations = vec![];
779 let mut short_start = true;
780 for ann in &line.annotations {
781 if let AnnotationType::MultilineStart(depth) = ann.annotation_type {
782 if source_string.chars().take(ann.start_col.file).all(|c| c.is_whitespace()) {
783 let uline = self.underline(ann.is_primary);
784 let chr = uline.multiline_whole_line;
785 annotations.push((depth, uline.style));
786 buffer_ops.push((line_offset, width_offset + depth - 1, chr, uline.style));
787 } else {
788 short_start = false;
789 break;
790 }
791 } else if let AnnotationType::MultilineLine(_) = ann.annotation_type {
792 } else {
793 short_start = false;
794 break;
795 }
796 }
797 if short_start {
798 for (y, x, c, s) in buffer_ops {
799 buffer.putc(y, x, c, s);
800 }
801 return annotations;
802 }
803
804 let mut annotations = line.annotations.clone();
837 annotations.sort_by_key(|a| Reverse(a.start_col));
838
839 let mut overlap = vec![false; annotations.len()];
902 let mut annotations_position = vec![];
903 let mut line_len: usize = 0;
904 let mut p = 0;
905 for (i, annotation) in annotations.iter().enumerate() {
906 for (j, next) in annotations.iter().enumerate() {
907 if overlaps(next, annotation, 0) && j > i {
908 overlap[i] = true;
909 overlap[j] = true;
910 }
911 if overlaps(next, annotation, 0) && annotation.has_label() && j > i && p == 0
915 {
917 if next.start_col == annotation.start_col
920 && next.end_col == annotation.end_col
921 && !next.has_label()
922 {
923 continue;
924 }
925
926 p += 1;
928 break;
929 }
930 }
931 annotations_position.push((p, annotation));
932 for (j, next) in annotations.iter().enumerate() {
933 if j > i {
934 let l = next.label.as_ref().map_or(0, |label| label.len() + 2);
935 if (overlaps(next, annotation, l) && annotation.has_label() && next.has_label()) || (annotation.takes_space() && next.has_label()) || (annotation.has_label() && next.takes_space())
952 || (annotation.takes_space() && next.takes_space())
953 || (overlaps(next, annotation, l)
954 && next.end_col <= annotation.end_col
955 && next.has_label()
956 && p == 0)
957 {
959 p += 1;
961 break;
962 }
963 }
964 }
965 line_len = max(line_len, p);
966 }
967
968 if line_len != 0 {
969 line_len += 1;
970 }
971
972 if line.annotations.iter().all(|a| a.is_line()) {
975 return vec![];
976 }
977
978 if annotations_position
979 .iter()
980 .all(|(_, ann)| matches!(ann.annotation_type, AnnotationType::MultilineStart(_)))
981 && let Some(max_pos) = annotations_position.iter().map(|(pos, _)| *pos).max()
982 {
983 for (pos, _) in &mut annotations_position {
996 *pos = max_pos - *pos;
997 }
998 line_len = line_len.saturating_sub(1);
1001 }
1002
1003 for pos in 0..=line_len {
1015 self.draw_col_separator_no_space(buffer, line_offset + pos + 1, width_offset - 2);
1016 }
1017 if close_window {
1018 self.draw_col_separator_end(buffer, line_offset + line_len + 1, width_offset - 2);
1019 }
1020
1021 for &(pos, annotation) in &annotations_position {
1034 let underline = self.underline(annotation.is_primary);
1035 let pos = pos + 1;
1036 match annotation.annotation_type {
1037 AnnotationType::MultilineStart(depth) | AnnotationType::MultilineEnd(depth) => {
1038 let pre: usize = source_string
1039 .chars()
1040 .take(annotation.start_col.file)
1041 .skip(left)
1042 .map(|c| char_width(c))
1043 .sum();
1044 self.draw_range(
1045 buffer,
1046 underline.multiline_horizontal,
1047 line_offset + pos,
1048 width_offset + depth,
1049 code_offset + pre,
1050 underline.style,
1051 );
1052 }
1053 _ => {}
1054 }
1055 }
1056
1057 for &(pos, annotation) in &annotations_position {
1069 let underline = self.underline(annotation.is_primary);
1070 let pos = pos + 1;
1071
1072 let code_offset = code_offset
1073 + source_string
1074 .chars()
1075 .take(annotation.start_col.file)
1076 .skip(left)
1077 .map(|c| char_width(c))
1078 .sum::<usize>();
1079 if pos > 1 && (annotation.has_label() || annotation.takes_space()) {
1080 for p in line_offset + 1..=line_offset + pos {
1081 buffer.putc(
1082 p,
1083 code_offset,
1084 match annotation.annotation_type {
1085 AnnotationType::MultilineLine(_) => underline.multiline_vertical,
1086 _ => underline.vertical_text_line,
1087 },
1088 underline.style,
1089 );
1090 }
1091 if let AnnotationType::MultilineStart(_) = annotation.annotation_type {
1092 buffer.putc(
1093 line_offset + pos,
1094 code_offset,
1095 underline.bottom_right,
1096 underline.style,
1097 );
1098 }
1099 if let AnnotationType::MultilineEnd(_) = annotation.annotation_type
1100 && annotation.has_label()
1101 {
1102 buffer.putc(
1103 line_offset + pos,
1104 code_offset,
1105 underline.multiline_bottom_right_with_text,
1106 underline.style,
1107 );
1108 }
1109 }
1110 match annotation.annotation_type {
1111 AnnotationType::MultilineStart(depth) => {
1112 buffer.putc(
1113 line_offset + pos,
1114 width_offset + depth - 1,
1115 underline.top_left,
1116 underline.style,
1117 );
1118 for p in line_offset + pos + 1..line_offset + line_len + 2 {
1119 buffer.putc(
1120 p,
1121 width_offset + depth - 1,
1122 underline.multiline_vertical,
1123 underline.style,
1124 );
1125 }
1126 }
1127 AnnotationType::MultilineEnd(depth) => {
1128 for p in line_offset..line_offset + pos {
1129 buffer.putc(
1130 p,
1131 width_offset + depth - 1,
1132 underline.multiline_vertical,
1133 underline.style,
1134 );
1135 }
1136 buffer.putc(
1137 line_offset + pos,
1138 width_offset + depth - 1,
1139 underline.bottom_left,
1140 underline.style,
1141 );
1142 }
1143 _ => (),
1144 }
1145 }
1146
1147 for &(pos, annotation) in &annotations_position {
1159 let style =
1160 if annotation.is_primary { Style::LabelPrimary } else { Style::LabelSecondary };
1161 let (pos, col) = if pos == 0 {
1162 let pre: usize = source_string
1163 .chars()
1164 .take(annotation.end_col.file)
1165 .skip(left)
1166 .map(|c| char_width(c))
1167 .sum();
1168 if annotation.end_col.file == 0 {
1169 (pos + 1, (pre + 2))
1170 } else {
1171 let pad = if annotation.end_col.file - annotation.start_col.file == 0 {
1172 2
1173 } else {
1174 1
1175 };
1176 (pos + 1, (pre + pad))
1177 }
1178 } else {
1179 let pre: usize = source_string
1180 .chars()
1181 .take(annotation.start_col.file)
1182 .skip(left)
1183 .map(|c| char_width(c))
1184 .sum();
1185 (pos + 2, pre)
1186 };
1187 if let Some(ref label) = annotation.label {
1188 buffer.puts(line_offset + pos, code_offset + col, label, style);
1189 }
1190 }
1191
1192 annotations_position.sort_by_key(|(_, ann)| {
1201 (Reverse(ann.len()), ann.is_primary)
1203 });
1204
1205 for &(pos, annotation) in &annotations_position {
1217 let uline = self.underline(annotation.is_primary);
1218 let width = annotation.end_col.file - annotation.start_col.file;
1219 let previous: String =
1220 source_string.chars().take(annotation.start_col.file).skip(left).collect();
1221 let underlined: String =
1222 source_string.chars().skip(annotation.start_col.file).take(width).collect();
1223 debug!(?previous, ?underlined);
1224 let code_offset = code_offset
1225 + source_string
1226 .chars()
1227 .take(annotation.start_col.file)
1228 .skip(left)
1229 .map(|c| char_width(c))
1230 .sum::<usize>();
1231 let ann_width: usize = source_string
1232 .chars()
1233 .skip(annotation.start_col.file)
1234 .take(width)
1235 .map(|c| char_width(c))
1236 .sum();
1237 let ann_width = if ann_width == 0
1238 && matches!(annotation.annotation_type, AnnotationType::Singleline)
1239 {
1240 1
1241 } else {
1242 ann_width
1243 };
1244 for p in 0..ann_width {
1245 buffer.putc(line_offset + 1, code_offset + p, uline.underline, uline.style);
1247 }
1248
1249 if pos == 0
1250 && matches!(
1251 annotation.annotation_type,
1252 AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_)
1253 )
1254 {
1255 buffer.putc(
1257 line_offset + 1,
1258 code_offset,
1259 match annotation.annotation_type {
1260 AnnotationType::MultilineStart(_) => uline.top_right_flat,
1261 AnnotationType::MultilineEnd(_) => uline.multiline_end_same_line,
1262 _ => panic!("unexpected annotation type: {annotation:?}"),
1263 },
1264 uline.style,
1265 );
1266 } else if pos != 0
1267 && matches!(
1268 annotation.annotation_type,
1269 AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_)
1270 )
1271 {
1272 buffer.putc(
1275 line_offset + 1,
1276 code_offset,
1277 match annotation.annotation_type {
1278 AnnotationType::MultilineStart(_) => uline.multiline_start_down,
1279 AnnotationType::MultilineEnd(_) => uline.multiline_end_up,
1280 _ => panic!("unexpected annotation type: {annotation:?}"),
1281 },
1282 uline.style,
1283 );
1284 } else if pos != 0 && annotation.has_label() {
1285 buffer.putc(line_offset + 1, code_offset, uline.label_start, uline.style);
1287 }
1288 }
1289
1290 for (i, (_pos, annotation)) in annotations_position.iter().enumerate() {
1294 if overlap[i] {
1296 continue;
1297 };
1298 let AnnotationType::Singleline = annotation.annotation_type else { continue };
1299 let width = annotation.end_col.display - annotation.start_col.display;
1300 if width > margin.column_width * 2 && width > 10 {
1301 let pad = max(margin.column_width / 3, 5);
1304 buffer.replace(
1306 line_offset,
1307 annotation.start_col.file + pad,
1308 annotation.end_col.file - pad,
1309 self.margin(),
1310 );
1311 buffer.replace(
1313 line_offset + 1,
1314 annotation.start_col.file + pad,
1315 annotation.end_col.file - pad,
1316 self.margin(),
1317 );
1318 }
1319 }
1320 annotations_position
1321 .iter()
1322 .filter_map(|&(_, annotation)| match annotation.annotation_type {
1323 AnnotationType::MultilineStart(p) | AnnotationType::MultilineEnd(p) => {
1324 let style = if annotation.is_primary {
1325 Style::LabelPrimary
1326 } else {
1327 Style::LabelSecondary
1328 };
1329 Some((p, style))
1330 }
1331 _ => None,
1332 })
1333 .collect::<Vec<_>>()
1334 }
1335
1336 fn get_multispan_max_line_num(&mut self, msp: &MultiSpan) -> usize {
1337 let Some(ref sm) = self.sm else {
1338 return 0;
1339 };
1340
1341 let will_be_emitted = |span: Span| {
1342 !span.is_dummy() && {
1343 let file = sm.lookup_source_file(span.hi());
1344 should_show_source_code(&self.ignored_directories_in_source_blocks, sm, &file)
1345 }
1346 };
1347
1348 let mut max = 0;
1349 for primary_span in msp.primary_spans() {
1350 if will_be_emitted(*primary_span) {
1351 let hi = sm.lookup_char_pos(primary_span.hi());
1352 max = (hi.line).max(max);
1353 }
1354 }
1355 if !self.short_message {
1356 for span_label in msp.span_labels() {
1357 if will_be_emitted(span_label.span) {
1358 let hi = sm.lookup_char_pos(span_label.span.hi());
1359 max = (hi.line).max(max);
1360 }
1361 }
1362 }
1363
1364 max
1365 }
1366
1367 fn get_max_line_num(&mut self, span: &MultiSpan, children: &[Subdiag]) -> usize {
1368 let primary = self.get_multispan_max_line_num(span);
1369 children
1370 .iter()
1371 .map(|sub| self.get_multispan_max_line_num(&sub.span))
1372 .max()
1373 .unwrap_or(0)
1374 .max(primary)
1375 }
1376
1377 fn msgs_to_buffer(
1380 &self,
1381 buffer: &mut StyledBuffer,
1382 msgs: &[(DiagMessage, Style)],
1383 args: &FluentArgs<'_>,
1384 padding: usize,
1385 label: &str,
1386 override_style: Option<Style>,
1387 ) -> usize {
1388 let padding = " ".repeat(padding + label.len() + 5);
1405
1406 fn style_or_override(style: Style, override_: Option<Style>) -> Style {
1408 match (style, override_) {
1409 (Style::NoStyle, Some(override_)) => override_,
1410 _ => style,
1411 }
1412 }
1413
1414 let mut line_number = 0;
1415
1416 for (text, style) in msgs.iter() {
1436 let text = self.translator.translate_message(text, args).map_err(Report::new).unwrap();
1437 let text = &normalize_whitespace(&text);
1438 let lines = text.split('\n').collect::<Vec<_>>();
1439 if lines.len() > 1 {
1440 for (i, line) in lines.iter().enumerate() {
1441 if i != 0 {
1442 line_number += 1;
1443 buffer.append(line_number, &padding, Style::NoStyle);
1444 }
1445 buffer.append(line_number, line, style_or_override(*style, override_style));
1446 }
1447 } else {
1448 buffer.append(line_number, text, style_or_override(*style, override_style));
1449 }
1450 }
1451 line_number
1452 }
1453
1454 #[instrument(level = "trace", skip(self, args), ret)]
1455 fn emit_messages_default_inner(
1456 &mut self,
1457 msp: &MultiSpan,
1458 msgs: &[(DiagMessage, Style)],
1459 args: &FluentArgs<'_>,
1460 code: &Option<ErrCode>,
1461 level: &Level,
1462 max_line_num_len: usize,
1463 is_secondary: bool,
1464 is_cont: bool,
1465 ) -> io::Result<CodeWindowStatus> {
1466 let mut buffer = StyledBuffer::new();
1467
1468 if !msp.has_primary_spans() && !msp.has_span_labels() && is_secondary && !self.short_message
1469 {
1470 for _ in 0..max_line_num_len {
1472 buffer.prepend(0, " ", Style::NoStyle);
1473 }
1474 self.draw_note_separator(&mut buffer, 0, max_line_num_len + 1, is_cont);
1475 if *level != Level::FailureNote {
1476 buffer.append(0, level.to_str(), Style::MainHeaderMsg);
1477 buffer.append(0, ": ", Style::NoStyle);
1478 }
1479 let printed_lines =
1480 self.msgs_to_buffer(&mut buffer, msgs, args, max_line_num_len, "note", None);
1481 if is_cont && matches!(self.theme, OutputTheme::Unicode) {
1482 for i in 1..=printed_lines {
1494 self.draw_col_separator_no_space(&mut buffer, i, max_line_num_len + 1);
1495 }
1496 }
1497 } else {
1498 let mut label_width = 0;
1499 if *level != Level::FailureNote {
1501 buffer.append(0, level.to_str(), Style::Level(*level));
1502 label_width += level.to_str().len();
1503 }
1504 if let Some(code) = code {
1505 buffer.append(0, "[", Style::Level(*level));
1506 let code = if let TerminalUrl::Yes = self.terminal_url {
1507 let path = "https://doc.rust-lang.org/error_codes";
1508 format!("\x1b]8;;{path}/{code}.html\x07{code}\x1b]8;;\x07")
1509 } else {
1510 code.to_string()
1511 };
1512 buffer.append(0, &code, Style::Level(*level));
1513 buffer.append(0, "]", Style::Level(*level));
1514 label_width += 2 + code.len();
1515 }
1516 let header_style = if is_secondary {
1517 Style::HeaderMsg
1518 } else if self.short_message {
1519 Style::NoStyle
1521 } else {
1522 Style::MainHeaderMsg
1523 };
1524 if *level != Level::FailureNote {
1525 buffer.append(0, ": ", header_style);
1526 label_width += 2;
1527 }
1528 let mut line = 0;
1529 for (text, style) in msgs.iter() {
1530 let text =
1531 self.translator.translate_message(text, args).map_err(Report::new).unwrap();
1532 for text in normalize_whitespace(&text).lines() {
1534 buffer.append(
1535 line,
1536 &format!(
1537 "{}{}",
1538 if line == 0 { String::new() } else { " ".repeat(label_width) },
1539 text
1540 ),
1541 match style {
1542 Style::Highlight => *style,
1543 _ => header_style,
1544 },
1545 );
1546 line += 1;
1547 }
1548 if line > 0 {
1554 line -= 1;
1555 }
1556 }
1557 if self.short_message {
1558 let labels = msp
1559 .span_labels()
1560 .into_iter()
1561 .filter_map(|label| match label.label {
1562 Some(msg) if label.is_primary => {
1563 let text = self.translator.translate_message(&msg, args).ok()?;
1564 if !text.trim().is_empty() { Some(text.to_string()) } else { None }
1565 }
1566 _ => None,
1567 })
1568 .collect::<Vec<_>>()
1569 .join(", ");
1570 if !labels.is_empty() {
1571 buffer.append(line, ": ", Style::NoStyle);
1572 buffer.append(line, &labels, Style::NoStyle);
1573 }
1574 }
1575 }
1576 let mut annotated_files = FileWithAnnotatedLines::collect_annotations(self, args, msp);
1577 trace!("{annotated_files:#?}");
1578 let mut code_window_status = CodeWindowStatus::Open;
1579
1580 let primary_span = msp.primary_span().unwrap_or_default();
1582 let (Some(sm), false) = (self.sm.as_ref(), primary_span.is_dummy()) else {
1583 return emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)
1585 .map(|_| code_window_status);
1586 };
1587 let primary_lo = sm.lookup_char_pos(primary_span.lo());
1588 if let Ok(pos) =
1589 annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
1590 {
1591 annotated_files.swap(0, pos);
1592 }
1593
1594 let mut col_sep_before_no_show_source = false;
1597 let annotated_files_len = annotated_files.len();
1598 for (file_idx, annotated_file) in annotated_files.into_iter().enumerate() {
1600 if !should_show_source_code(
1602 &self.ignored_directories_in_source_blocks,
1603 sm,
1604 &annotated_file.file,
1605 ) {
1606 if !self.short_message {
1607 if col_sep_before_no_show_source {
1618 let buffer_msg_line_offset = buffer.num_lines();
1619 self.draw_col_separator_end(
1620 &mut buffer,
1621 buffer_msg_line_offset,
1622 max_line_num_len + 1,
1623 );
1624 }
1625 col_sep_before_no_show_source = false;
1626
1627 for (annotation_id, line) in annotated_file.lines.iter().enumerate() {
1629 let mut annotations = line.annotations.clone();
1630 annotations.sort_by_key(|a| Reverse(a.start_col));
1631 let mut line_idx = buffer.num_lines();
1632
1633 let labels: Vec<_> = annotations
1634 .iter()
1635 .filter_map(|a| Some((a.label.as_ref()?, a.is_primary)))
1636 .filter(|(l, _)| !l.is_empty())
1637 .collect();
1638
1639 if annotation_id == 0 || !labels.is_empty() {
1640 buffer.append(
1641 line_idx,
1642 &format!(
1643 "{}:{}:{}",
1644 sm.filename_for_diagnostics(&annotated_file.file.name),
1645 sm.doctest_offset_line(
1646 &annotated_file.file.name,
1647 line.line_index
1648 ),
1649 annotations[0].start_col.file + 1,
1650 ),
1651 Style::LineAndColumn,
1652 );
1653 if annotation_id == 0 {
1654 buffer.prepend(line_idx, self.file_start(), Style::LineNumber);
1655 } else {
1656 buffer.prepend(
1657 line_idx,
1658 self.secondary_file_start(),
1659 Style::LineNumber,
1660 );
1661 }
1662 for _ in 0..max_line_num_len {
1663 buffer.prepend(line_idx, " ", Style::NoStyle);
1664 }
1665 line_idx += 1;
1666 }
1667 if is_cont
1668 && file_idx == annotated_files_len - 1
1669 && annotation_id == annotated_file.lines.len() - 1
1670 && !labels.is_empty()
1671 {
1672 code_window_status = CodeWindowStatus::Closed;
1673 }
1674 let labels_len = labels.len();
1675 for (label_idx, (label, is_primary)) in labels.into_iter().enumerate() {
1676 let style = if is_primary {
1677 Style::LabelPrimary
1678 } else {
1679 Style::LabelSecondary
1680 };
1681 self.draw_col_separator_no_space(
1682 &mut buffer,
1683 line_idx,
1684 max_line_num_len + 1,
1685 );
1686 line_idx += 1;
1687 self.draw_note_separator(
1688 &mut buffer,
1689 line_idx,
1690 max_line_num_len + 1,
1691 label_idx != labels_len - 1,
1692 );
1693 buffer.append(line_idx, "note", Style::MainHeaderMsg);
1694 buffer.append(line_idx, ": ", Style::NoStyle);
1695 buffer.append(line_idx, label, style);
1696 line_idx += 1;
1697 }
1698 }
1699 }
1700 continue;
1701 } else {
1702 col_sep_before_no_show_source = true;
1703 }
1704
1705 let is_primary = primary_lo.file.name == annotated_file.file.name;
1708 if is_primary {
1709 let loc = primary_lo.clone();
1710 if !self.short_message {
1711 let buffer_msg_line_offset = buffer.num_lines();
1713
1714 buffer.prepend(buffer_msg_line_offset, self.file_start(), Style::LineNumber);
1715 buffer.append(
1716 buffer_msg_line_offset,
1717 &format!(
1718 "{}:{}:{}",
1719 sm.filename_for_diagnostics(&loc.file.name),
1720 sm.doctest_offset_line(&loc.file.name, loc.line),
1721 loc.col.0 + 1,
1722 ),
1723 Style::LineAndColumn,
1724 );
1725 for _ in 0..max_line_num_len {
1726 buffer.prepend(buffer_msg_line_offset, " ", Style::NoStyle);
1727 }
1728 } else {
1729 buffer.prepend(
1730 0,
1731 &format!(
1732 "{}:{}:{}: ",
1733 sm.filename_for_diagnostics(&loc.file.name),
1734 sm.doctest_offset_line(&loc.file.name, loc.line),
1735 loc.col.0 + 1,
1736 ),
1737 Style::LineAndColumn,
1738 );
1739 }
1740 } else if !self.short_message {
1741 let buffer_msg_line_offset = buffer.num_lines();
1743
1744 self.draw_col_separator_no_space(
1755 &mut buffer,
1756 buffer_msg_line_offset,
1757 max_line_num_len + 1,
1758 );
1759
1760 buffer.prepend(
1762 buffer_msg_line_offset + 1,
1763 self.secondary_file_start(),
1764 Style::LineNumber,
1765 );
1766 let loc = if let Some(first_line) = annotated_file.lines.first() {
1767 let col = if let Some(first_annotation) = first_line.annotations.first() {
1768 format!(":{}", first_annotation.start_col.file + 1)
1769 } else {
1770 String::new()
1771 };
1772 format!(
1773 "{}:{}{}",
1774 sm.filename_for_diagnostics(&annotated_file.file.name),
1775 sm.doctest_offset_line(&annotated_file.file.name, first_line.line_index),
1776 col
1777 )
1778 } else {
1779 format!("{}", sm.filename_for_diagnostics(&annotated_file.file.name))
1780 };
1781 buffer.append(buffer_msg_line_offset + 1, &loc, Style::LineAndColumn);
1782 for _ in 0..max_line_num_len {
1783 buffer.prepend(buffer_msg_line_offset + 1, " ", Style::NoStyle);
1784 }
1785 }
1786
1787 if !self.short_message {
1788 let buffer_msg_line_offset = buffer.num_lines();
1790 self.draw_col_separator_no_space(
1791 &mut buffer,
1792 buffer_msg_line_offset,
1793 max_line_num_len + 1,
1794 );
1795
1796 let mut multilines = FxIndexMap::default();
1798
1799 let mut whitespace_margin = usize::MAX;
1801 for line_idx in 0..annotated_file.lines.len() {
1802 let file = Arc::clone(&annotated_file.file);
1803 let line = &annotated_file.lines[line_idx];
1804 if let Some(source_string) =
1805 line.line_index.checked_sub(1).and_then(|l| file.get_line(l))
1806 {
1807 let leading_whitespace = source_string
1814 .chars()
1815 .take_while(|c| rustc_lexer::is_whitespace(*c))
1816 .count();
1817 if source_string.chars().any(|c| !rustc_lexer::is_whitespace(c)) {
1818 whitespace_margin = min(whitespace_margin, leading_whitespace);
1819 }
1820 }
1821 }
1822 if whitespace_margin == usize::MAX {
1823 whitespace_margin = 0;
1824 }
1825
1826 let mut span_left_margin = usize::MAX;
1828 for line in &annotated_file.lines {
1829 for ann in &line.annotations {
1830 span_left_margin = min(span_left_margin, ann.start_col.file);
1831 span_left_margin = min(span_left_margin, ann.end_col.file);
1832 }
1833 }
1834 if span_left_margin == usize::MAX {
1835 span_left_margin = 0;
1836 }
1837
1838 let mut span_right_margin = 0;
1840 let mut label_right_margin = 0;
1841 let mut max_line_len = 0;
1842 for line in &annotated_file.lines {
1843 max_line_len = max(
1844 max_line_len,
1845 line.line_index
1846 .checked_sub(1)
1847 .and_then(|l| annotated_file.file.get_line(l))
1848 .map_or(0, |s| s.len()),
1849 );
1850 for ann in &line.annotations {
1851 span_right_margin = max(span_right_margin, ann.start_col.file);
1852 span_right_margin = max(span_right_margin, ann.end_col.file);
1853 let label_right = ann.label.as_ref().map_or(0, |l| l.len() + 1);
1855 label_right_margin =
1856 max(label_right_margin, ann.end_col.file + label_right);
1857 }
1858 }
1859
1860 let width_offset = 3 + max_line_num_len;
1861 let code_offset = if annotated_file.multiline_depth == 0 {
1862 width_offset
1863 } else {
1864 width_offset + annotated_file.multiline_depth + 1
1865 };
1866
1867 let column_width = self.column_width(code_offset);
1868
1869 let margin = Margin::new(
1870 whitespace_margin,
1871 span_left_margin,
1872 span_right_margin,
1873 label_right_margin,
1874 column_width,
1875 max_line_len,
1876 );
1877
1878 for line_idx in 0..annotated_file.lines.len() {
1880 let previous_buffer_line = buffer.num_lines();
1881
1882 let depths = self.render_source_line(
1883 &mut buffer,
1884 Arc::clone(&annotated_file.file),
1885 &annotated_file.lines[line_idx],
1886 width_offset,
1887 code_offset,
1888 margin,
1889 !is_cont
1890 && file_idx + 1 == annotated_files_len
1891 && line_idx + 1 == annotated_file.lines.len(),
1892 );
1893
1894 let mut to_add = FxIndexMap::default();
1895
1896 for (depth, style) in depths {
1897 if multilines.swap_remove(&depth).is_none() {
1899 to_add.insert(depth, style);
1900 }
1901 }
1902
1903 for (depth, style) in &multilines {
1906 for line in previous_buffer_line..buffer.num_lines() {
1907 self.draw_multiline_line(
1908 &mut buffer,
1909 line,
1910 width_offset,
1911 *depth,
1912 *style,
1913 );
1914 }
1915 }
1916 if line_idx < (annotated_file.lines.len() - 1) {
1919 let line_idx_delta = annotated_file.lines[line_idx + 1].line_index
1920 - annotated_file.lines[line_idx].line_index;
1921 if line_idx_delta > 2 {
1922 let last_buffer_line_num = buffer.num_lines();
1923 self.draw_line_separator(
1924 &mut buffer,
1925 last_buffer_line_num,
1926 width_offset,
1927 );
1928
1929 for (depth, style) in &multilines {
1931 self.draw_multiline_line(
1932 &mut buffer,
1933 last_buffer_line_num,
1934 width_offset,
1935 *depth,
1936 *style,
1937 );
1938 }
1939 if let Some(line) = annotated_file.lines.get(line_idx) {
1940 for ann in &line.annotations {
1941 if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1942 {
1943 self.draw_multiline_line(
1947 &mut buffer,
1948 last_buffer_line_num,
1949 width_offset,
1950 pos,
1951 if ann.is_primary {
1952 Style::UnderlinePrimary
1953 } else {
1954 Style::UnderlineSecondary
1955 },
1956 );
1957 }
1958 }
1959 }
1960 } else if line_idx_delta == 2 {
1961 let unannotated_line = annotated_file
1962 .file
1963 .get_line(annotated_file.lines[line_idx].line_index)
1964 .unwrap_or_else(|| Cow::from(""));
1965
1966 let last_buffer_line_num = buffer.num_lines();
1967
1968 self.draw_line(
1969 &mut buffer,
1970 &normalize_whitespace(&unannotated_line),
1971 annotated_file.lines[line_idx + 1].line_index - 1,
1972 last_buffer_line_num,
1973 width_offset,
1974 code_offset,
1975 margin,
1976 );
1977
1978 for (depth, style) in &multilines {
1979 self.draw_multiline_line(
1980 &mut buffer,
1981 last_buffer_line_num,
1982 width_offset,
1983 *depth,
1984 *style,
1985 );
1986 }
1987 if let Some(line) = annotated_file.lines.get(line_idx) {
1988 for ann in &line.annotations {
1989 if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1990 {
1991 self.draw_multiline_line(
1992 &mut buffer,
1993 last_buffer_line_num,
1994 width_offset,
1995 pos,
1996 if ann.is_primary {
1997 Style::UnderlinePrimary
1998 } else {
1999 Style::UnderlineSecondary
2000 },
2001 );
2002 }
2003 }
2004 }
2005 }
2006 }
2007
2008 multilines.extend(&to_add);
2009 }
2010 }
2011 trace!("buffer: {:#?}", buffer.render());
2012 }
2013
2014 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
2016
2017 Ok(code_window_status)
2018 }
2019
2020 fn column_width(&self, code_offset: usize) -> usize {
2021 if let Some(width) = self.diagnostic_width {
2022 width.saturating_sub(code_offset)
2023 } else if self.ui_testing || cfg!(miri) {
2024 DEFAULT_COLUMN_WIDTH
2025 } else {
2026 termize::dimensions()
2027 .map(|(w, _)| w.saturating_sub(code_offset))
2028 .unwrap_or(DEFAULT_COLUMN_WIDTH)
2029 }
2030 }
2031
2032 fn emit_suggestion_default(
2033 &mut self,
2034 span: &MultiSpan,
2035 suggestion: &CodeSuggestion,
2036 args: &FluentArgs<'_>,
2037 level: &Level,
2038 max_line_num_len: usize,
2039 ) -> io::Result<()> {
2040 let Some(ref sm) = self.sm else {
2041 return Ok(());
2042 };
2043
2044 let suggestions = suggestion.splice_lines(sm);
2046 debug!(?suggestions);
2047
2048 if suggestions.is_empty() {
2049 return Ok(());
2055 }
2056
2057 let mut buffer = StyledBuffer::new();
2058
2059 buffer.append(0, level.to_str(), Style::Level(*level));
2061 buffer.append(0, ": ", Style::HeaderMsg);
2062
2063 let mut msg = vec![(suggestion.msg.to_owned(), Style::NoStyle)];
2064 if let Some(confusion_type) =
2065 suggestions.iter().take(MAX_SUGGESTIONS).find_map(|(_, _, _, confusion_type)| {
2066 if confusion_type.has_confusion() { Some(*confusion_type) } else { None }
2067 })
2068 {
2069 msg.push((confusion_type.label_text().into(), Style::NoStyle));
2070 }
2071 self.msgs_to_buffer(
2072 &mut buffer,
2073 &msg,
2074 args,
2075 max_line_num_len,
2076 "suggestion",
2077 Some(Style::HeaderMsg),
2078 );
2079
2080 let other_suggestions = suggestions.len().saturating_sub(MAX_SUGGESTIONS);
2081
2082 let mut row_num = 2;
2083 for (i, (complete, parts, highlights, _)) in
2084 suggestions.into_iter().enumerate().take(MAX_SUGGESTIONS)
2085 {
2086 debug!(?complete, ?parts, ?highlights);
2087
2088 let has_deletion =
2089 parts.iter().any(|p| p.is_deletion(sm) || p.is_destructive_replacement(sm));
2090 let is_multiline = complete.lines().count() > 1;
2091
2092 if i == 0 {
2093 self.draw_col_separator_start(&mut buffer, row_num - 1, max_line_num_len + 1);
2094 } else {
2095 buffer.puts(
2096 row_num - 1,
2097 max_line_num_len + 1,
2098 self.multi_suggestion_separator(),
2099 Style::LineNumber,
2100 );
2101 }
2102 if let Some(span) = span.primary_span() {
2103 let loc = sm.lookup_char_pos(parts[0].span.lo());
2108 if (span.is_dummy() || loc.file.name != sm.span_to_filename(span))
2109 && loc.file.name.is_real()
2110 {
2111 let arrow = self.file_start();
2114 buffer.puts(row_num - 1, 0, arrow, Style::LineNumber);
2115 let filename = sm.filename_for_diagnostics(&loc.file.name);
2116 let offset = sm.doctest_offset_line(&loc.file.name, loc.line);
2117 let message = format!("{}:{}:{}", filename, offset, loc.col.0 + 1);
2118 if row_num == 2 {
2119 let col = usize::max(max_line_num_len + 1, arrow.len());
2120 buffer.puts(1, col, &message, Style::LineAndColumn);
2121 } else {
2122 buffer.append(row_num - 1, &message, Style::LineAndColumn);
2123 }
2124 for _ in 0..max_line_num_len {
2125 buffer.prepend(row_num - 1, " ", Style::NoStyle);
2126 }
2127 self.draw_col_separator_no_space(&mut buffer, row_num, max_line_num_len + 1);
2128 row_num += 1;
2129 }
2130 }
2131 let show_code_change = if has_deletion && !is_multiline {
2132 DisplaySuggestion::Diff
2133 } else if let [part] = &parts[..]
2134 && part.snippet.ends_with('\n')
2135 && part.snippet.trim() == complete.trim()
2136 {
2137 DisplaySuggestion::Add
2139 } else if (parts.len() != 1 || parts[0].snippet.trim() != complete.trim())
2140 && !is_multiline
2141 {
2142 DisplaySuggestion::Underline
2143 } else {
2144 DisplaySuggestion::None
2145 };
2146
2147 if let DisplaySuggestion::Diff = show_code_change {
2148 row_num += 1;
2149 }
2150
2151 let file_lines = sm
2152 .span_to_lines(parts[0].span)
2153 .expect("span_to_lines failed when emitting suggestion");
2154
2155 assert!(!file_lines.lines.is_empty() || parts[0].span.is_dummy());
2156
2157 let line_start = sm.lookup_char_pos(parts[0].span.lo()).line;
2158 let mut lines = complete.lines();
2159 if lines.clone().next().is_none() {
2160 let line_end = sm.lookup_char_pos(parts[0].span.hi()).line;
2162 for line in line_start..=line_end {
2163 self.draw_line_num(
2164 &mut buffer,
2165 line,
2166 row_num - 1 + line - line_start,
2167 max_line_num_len,
2168 );
2169 buffer.puts(
2170 row_num - 1 + line - line_start,
2171 max_line_num_len + 1,
2172 "- ",
2173 Style::Removal,
2174 );
2175 buffer.puts(
2176 row_num - 1 + line - line_start,
2177 max_line_num_len + 3,
2178 &normalize_whitespace(&file_lines.file.get_line(line - 1).unwrap()),
2179 Style::Removal,
2180 );
2181 }
2182 row_num += line_end - line_start;
2183 }
2184 let mut unhighlighted_lines = Vec::new();
2185 let mut last_pos = 0;
2186 let mut is_item_attribute = false;
2187 for (line_pos, (line, highlight_parts)) in lines.by_ref().zip(highlights).enumerate() {
2188 last_pos = line_pos;
2189 debug!(%line_pos, %line, ?highlight_parts);
2190
2191 if highlight_parts.is_empty() {
2193 unhighlighted_lines.push((line_pos, line));
2194 continue;
2195 }
2196 if highlight_parts.len() == 1
2197 && line.trim().starts_with("#[")
2198 && line.trim().ends_with(']')
2199 {
2200 is_item_attribute = true;
2201 }
2202
2203 match unhighlighted_lines.len() {
2204 0 => (),
2205 n if n <= 3 => unhighlighted_lines.drain(..).for_each(|(p, l)| {
2210 self.draw_code_line(
2211 &mut buffer,
2212 &mut row_num,
2213 &[],
2214 p + line_start,
2215 l,
2216 show_code_change,
2217 max_line_num_len,
2218 &file_lines,
2219 is_multiline,
2220 )
2221 }),
2222 _ => {
2230 let last_line = unhighlighted_lines.pop();
2231 let first_line = unhighlighted_lines.drain(..).next();
2232
2233 if let Some((p, l)) = first_line {
2234 self.draw_code_line(
2235 &mut buffer,
2236 &mut row_num,
2237 &[],
2238 p + line_start,
2239 l,
2240 show_code_change,
2241 max_line_num_len,
2242 &file_lines,
2243 is_multiline,
2244 )
2245 }
2246
2247 let placeholder = self.margin();
2248 let padding = str_width(placeholder);
2249 buffer.puts(
2250 row_num,
2251 max_line_num_len.saturating_sub(padding),
2252 placeholder,
2253 Style::LineNumber,
2254 );
2255 row_num += 1;
2256
2257 if let Some((p, l)) = last_line {
2258 self.draw_code_line(
2259 &mut buffer,
2260 &mut row_num,
2261 &[],
2262 p + line_start,
2263 l,
2264 show_code_change,
2265 max_line_num_len,
2266 &file_lines,
2267 is_multiline,
2268 )
2269 }
2270 }
2271 }
2272
2273 self.draw_code_line(
2274 &mut buffer,
2275 &mut row_num,
2276 &highlight_parts,
2277 line_pos + line_start,
2278 line,
2279 show_code_change,
2280 max_line_num_len,
2281 &file_lines,
2282 is_multiline,
2283 )
2284 }
2285 if let DisplaySuggestion::Add = show_code_change
2286 && is_item_attribute
2287 {
2288 let file_lines = sm
2295 .span_to_lines(parts[0].span.shrink_to_hi())
2296 .expect("span_to_lines failed when emitting suggestion");
2297 let line_num = sm.lookup_char_pos(parts[0].span.lo()).line;
2298 if let Some(line) = file_lines.file.get_line(line_num - 1) {
2299 let line = normalize_whitespace(&line);
2300 self.draw_code_line(
2301 &mut buffer,
2302 &mut row_num,
2303 &[],
2304 line_num + last_pos + 1,
2305 &line,
2306 DisplaySuggestion::None,
2307 max_line_num_len,
2308 &file_lines,
2309 is_multiline,
2310 )
2311 }
2312 }
2313
2314 let mut offsets: Vec<(usize, isize)> = Vec::new();
2317 if let DisplaySuggestion::Diff | DisplaySuggestion::Underline | DisplaySuggestion::Add =
2320 show_code_change
2321 {
2322 for part in parts {
2323 let snippet = if let Ok(snippet) = sm.span_to_snippet(part.span) {
2324 snippet
2325 } else {
2326 String::new()
2327 };
2328 let span_start_pos = sm.lookup_char_pos(part.span.lo()).col_display;
2329 let span_end_pos = sm.lookup_char_pos(part.span.hi()).col_display;
2330
2331 let is_whitespace_addition = part.snippet.trim().is_empty();
2334
2335 let start = if is_whitespace_addition {
2337 0
2338 } else {
2339 part.snippet.len().saturating_sub(part.snippet.trim_start().len())
2340 };
2341 let sub_len: usize = str_width(if is_whitespace_addition {
2344 &part.snippet
2345 } else {
2346 part.snippet.trim()
2347 });
2348
2349 let offset: isize = offsets
2350 .iter()
2351 .filter_map(
2352 |(start, v)| if span_start_pos < *start { None } else { Some(v) },
2353 )
2354 .sum();
2355 let underline_start = (span_start_pos + start) as isize + offset;
2356 let underline_end = (span_start_pos + start + sub_len) as isize + offset;
2357 let padding: usize = max_line_num_len + 3;
2358 for p in underline_start..underline_end {
2359 if let DisplaySuggestion::Underline = show_code_change
2360 && is_different(sm, &part.snippet, part.span)
2361 {
2362 buffer.putc(
2365 row_num,
2366 (padding as isize + p) as usize,
2367 if part.is_addition(sm) { '+' } else { self.diff() },
2368 Style::Addition,
2369 );
2370 }
2371 }
2372 if let DisplaySuggestion::Diff = show_code_change {
2373 let newlines = snippet.lines().count();
2404 if newlines > 0 && row_num > newlines {
2405 for (i, line) in snippet.lines().enumerate() {
2414 let line = normalize_whitespace(line);
2415 let row = row_num - 2 - (newlines - i - 1);
2416 let start = if i == 0 {
2422 (padding as isize + span_start_pos as isize) as usize
2423 } else {
2424 padding
2425 };
2426 let end = if i == 0 {
2427 (padding as isize
2428 + span_start_pos as isize
2429 + line.len() as isize)
2430 as usize
2431 } else if i == newlines - 1 {
2432 (padding as isize + span_end_pos as isize) as usize
2433 } else {
2434 (padding as isize + line.len() as isize) as usize
2435 };
2436 buffer.set_style_range(row, start, end, Style::Removal, true);
2437 }
2438 } else {
2439 buffer.set_style_range(
2441 row_num - 2,
2442 (padding as isize + span_start_pos as isize) as usize,
2443 (padding as isize + span_end_pos as isize) as usize,
2444 Style::Removal,
2445 true,
2446 );
2447 }
2448 }
2449
2450 let full_sub_len = str_width(&part.snippet) as isize;
2452
2453 let snippet_len = span_end_pos as isize - span_start_pos as isize;
2455 offsets.push((span_end_pos, full_sub_len - snippet_len));
2459 }
2460 row_num += 1;
2461 }
2462
2463 if lines.next().is_some() {
2465 let placeholder = self.margin();
2466 let padding = str_width(placeholder);
2467 buffer.puts(
2468 row_num,
2469 max_line_num_len.saturating_sub(padding),
2470 placeholder,
2471 Style::LineNumber,
2472 );
2473 } else {
2474 let row = match show_code_change {
2475 DisplaySuggestion::Diff
2476 | DisplaySuggestion::Add
2477 | DisplaySuggestion::Underline => row_num - 1,
2478 DisplaySuggestion::None => row_num,
2479 };
2480 if other_suggestions > 0 {
2481 self.draw_col_separator_no_space(&mut buffer, row, max_line_num_len + 1);
2482 } else {
2483 self.draw_col_separator_end(&mut buffer, row, max_line_num_len + 1);
2484 }
2485 row_num = row + 1;
2486 }
2487 }
2488 if other_suggestions > 0 {
2489 self.draw_note_separator(&mut buffer, row_num, max_line_num_len + 1, false);
2490 let msg = format!(
2491 "and {} other candidate{}",
2492 other_suggestions,
2493 pluralize!(other_suggestions)
2494 );
2495 buffer.append(row_num, &msg, Style::NoStyle);
2496 }
2497
2498 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
2499 Ok(())
2500 }
2501
2502 #[instrument(level = "trace", skip(self, args, code, children, suggestions))]
2503 fn emit_messages_default(
2504 &mut self,
2505 level: &Level,
2506 messages: &[(DiagMessage, Style)],
2507 args: &FluentArgs<'_>,
2508 code: &Option<ErrCode>,
2509 span: &MultiSpan,
2510 children: &[Subdiag],
2511 suggestions: &[CodeSuggestion],
2512 ) {
2513 let max_line_num_len = if self.ui_testing {
2514 ANONYMIZED_LINE_NUM.len()
2515 } else {
2516 let n = self.get_max_line_num(span, children);
2517 num_decimal_digits(n)
2518 };
2519
2520 match self.emit_messages_default_inner(
2521 span,
2522 messages,
2523 args,
2524 code,
2525 level,
2526 max_line_num_len,
2527 false,
2528 !children.is_empty()
2529 || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden),
2530 ) {
2531 Ok(code_window_status) => {
2532 if !children.is_empty()
2533 || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden)
2534 {
2535 let mut buffer = StyledBuffer::new();
2536 if !self.short_message {
2537 if let Some(child) = children.iter().next()
2538 && child.span.primary_spans().is_empty()
2539 {
2540 self.draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1);
2542 } else if matches!(code_window_status, CodeWindowStatus::Open) {
2543 self.draw_col_separator_end(&mut buffer, 0, max_line_num_len + 1);
2545 }
2546 }
2547 if let Err(e) = emit_to_destination(
2548 &buffer.render(),
2549 level,
2550 &mut self.dst,
2551 self.short_message,
2552 ) {
2553 panic!("failed to emit error: {e}")
2554 }
2555 }
2556 if !self.short_message {
2557 for (i, child) in children.iter().enumerate() {
2558 assert!(child.level.can_be_subdiag());
2559 let span = &child.span;
2560 let should_close = match children.get(i + 1) {
2562 Some(c) => !c.span.primary_spans().is_empty(),
2563 None => i + 1 == children.len(),
2564 };
2565 if let Err(err) = self.emit_messages_default_inner(
2566 span,
2567 &child.messages,
2568 args,
2569 &None,
2570 &child.level,
2571 max_line_num_len,
2572 true,
2573 !should_close,
2574 ) {
2575 panic!("failed to emit error: {err}");
2576 }
2577 }
2578 for (i, sugg) in suggestions.iter().enumerate() {
2579 match sugg.style {
2580 SuggestionStyle::CompletelyHidden => {
2581 }
2583 SuggestionStyle::HideCodeAlways => {
2584 if let Err(e) = self.emit_messages_default_inner(
2585 &MultiSpan::new(),
2586 &[(sugg.msg.to_owned(), Style::HeaderMsg)],
2587 args,
2588 &None,
2589 &Level::Help,
2590 max_line_num_len,
2591 true,
2592 i + 1 != suggestions.len(),
2595 ) {
2596 panic!("failed to emit error: {e}");
2597 }
2598 }
2599 SuggestionStyle::HideCodeInline
2600 | SuggestionStyle::ShowCode
2601 | SuggestionStyle::ShowAlways => {
2602 if let Err(e) = self.emit_suggestion_default(
2603 span,
2604 sugg,
2605 args,
2606 &Level::Help,
2607 max_line_num_len,
2608 ) {
2609 panic!("failed to emit error: {e}");
2610 }
2611 }
2612 }
2613 }
2614 }
2615 }
2616 Err(e) => panic!("failed to emit error: {e}"),
2617 }
2618
2619 match writeln!(self.dst) {
2620 Err(e) => panic!("failed to emit error: {e}"),
2621 _ => {
2622 if let Err(e) = self.dst.flush() {
2623 panic!("failed to emit error: {e}")
2624 }
2625 }
2626 }
2627 }
2628
2629 fn draw_code_line(
2630 &self,
2631 buffer: &mut StyledBuffer,
2632 row_num: &mut usize,
2633 highlight_parts: &[SubstitutionHighlight],
2634 line_num: usize,
2635 line_to_add: &str,
2636 show_code_change: DisplaySuggestion,
2637 max_line_num_len: usize,
2638 file_lines: &FileLines,
2639 is_multiline: bool,
2640 ) {
2641 if let DisplaySuggestion::Diff = show_code_change {
2642 let lines_to_remove = file_lines.lines.iter().take(file_lines.lines.len() - 1);
2645 for (index, line_to_remove) in lines_to_remove.enumerate() {
2646 self.draw_line_num(buffer, line_num + index, *row_num - 1, max_line_num_len);
2647 buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2648 let line = normalize_whitespace(
2649 &file_lines.file.get_line(line_to_remove.line_index).unwrap(),
2650 );
2651 buffer.puts(*row_num - 1, max_line_num_len + 3, &line, Style::NoStyle);
2652 *row_num += 1;
2653 }
2654 let last_line_index = file_lines.lines[file_lines.lines.len() - 1].line_index;
2661 let last_line = &file_lines.file.get_line(last_line_index).unwrap();
2662 if last_line != line_to_add {
2663 self.draw_line_num(
2664 buffer,
2665 line_num + file_lines.lines.len() - 1,
2666 *row_num - 1,
2667 max_line_num_len,
2668 );
2669 buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2670 buffer.puts(
2671 *row_num - 1,
2672 max_line_num_len + 3,
2673 &normalize_whitespace(last_line),
2674 Style::NoStyle,
2675 );
2676 if !line_to_add.trim().is_empty() {
2677 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2691 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2692 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2693 } else {
2694 *row_num -= 1;
2695 }
2696 } else {
2697 *row_num -= 2;
2698 }
2699 } else if is_multiline {
2700 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2701 match &highlight_parts {
2702 [SubstitutionHighlight { start: 0, end }] if *end == line_to_add.len() => {
2703 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2704 }
2705 [] => {
2706 self.draw_col_separator_no_space(buffer, *row_num, max_line_num_len + 1);
2708 }
2709 _ => {
2710 let diff = self.diff();
2711 buffer.puts(
2712 *row_num,
2713 max_line_num_len + 1,
2714 &format!("{diff} "),
2715 Style::Addition,
2716 );
2717 }
2718 }
2719 buffer.puts(
2725 *row_num,
2726 max_line_num_len + 3,
2727 &normalize_whitespace(line_to_add),
2728 Style::NoStyle,
2729 );
2730 } else if let DisplaySuggestion::Add = show_code_change {
2731 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2732 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2733 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2734 } else {
2735 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2736 self.draw_col_separator(buffer, *row_num, max_line_num_len + 1);
2737 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2738 }
2739
2740 for &SubstitutionHighlight { start, end } in highlight_parts {
2742 if start != end {
2744 let tabs: usize = line_to_add
2746 .chars()
2747 .take(start)
2748 .map(|ch| match ch {
2749 '\t' => 3,
2750 _ => 0,
2751 })
2752 .sum();
2753 buffer.set_style_range(
2754 *row_num,
2755 max_line_num_len + 3 + start + tabs,
2756 max_line_num_len + 3 + end + tabs,
2757 Style::Addition,
2758 true,
2759 );
2760 }
2761 }
2762 *row_num += 1;
2763 }
2764
2765 fn underline(&self, is_primary: bool) -> UnderlineParts {
2766 match (self.theme, is_primary) {
2791 (OutputTheme::Ascii, true) => UnderlineParts {
2792 style: Style::UnderlinePrimary,
2793 underline: '^',
2794 label_start: '^',
2795 vertical_text_line: '|',
2796 multiline_vertical: '|',
2797 multiline_horizontal: '_',
2798 multiline_whole_line: '/',
2799 multiline_start_down: '^',
2800 bottom_right: '|',
2801 top_left: ' ',
2802 top_right_flat: '^',
2803 bottom_left: '|',
2804 multiline_end_up: '^',
2805 multiline_end_same_line: '^',
2806 multiline_bottom_right_with_text: '|',
2807 },
2808 (OutputTheme::Ascii, false) => UnderlineParts {
2809 style: Style::UnderlineSecondary,
2810 underline: '-',
2811 label_start: '-',
2812 vertical_text_line: '|',
2813 multiline_vertical: '|',
2814 multiline_horizontal: '_',
2815 multiline_whole_line: '/',
2816 multiline_start_down: '-',
2817 bottom_right: '|',
2818 top_left: ' ',
2819 top_right_flat: '-',
2820 bottom_left: '|',
2821 multiline_end_up: '-',
2822 multiline_end_same_line: '-',
2823 multiline_bottom_right_with_text: '|',
2824 },
2825 (OutputTheme::Unicode, true) => UnderlineParts {
2826 style: Style::UnderlinePrimary,
2827 underline: '━',
2828 label_start: '┯',
2829 vertical_text_line: '│',
2830 multiline_vertical: '┃',
2831 multiline_horizontal: '━',
2832 multiline_whole_line: '┏',
2833 multiline_start_down: '╿',
2834 bottom_right: '┙',
2835 top_left: '┏',
2836 top_right_flat: '┛',
2837 bottom_left: '┗',
2838 multiline_end_up: '╿',
2839 multiline_end_same_line: '┛',
2840 multiline_bottom_right_with_text: '┥',
2841 },
2842 (OutputTheme::Unicode, false) => UnderlineParts {
2843 style: Style::UnderlineSecondary,
2844 underline: '─',
2845 label_start: '┬',
2846 vertical_text_line: '│',
2847 multiline_vertical: '│',
2848 multiline_horizontal: '─',
2849 multiline_whole_line: '┌',
2850 multiline_start_down: '│',
2851 bottom_right: '┘',
2852 top_left: '┌',
2853 top_right_flat: '┘',
2854 bottom_left: '└',
2855 multiline_end_up: '│',
2856 multiline_end_same_line: '┘',
2857 multiline_bottom_right_with_text: '┤',
2858 },
2859 }
2860 }
2861
2862 fn col_separator(&self) -> char {
2863 match self.theme {
2864 OutputTheme::Ascii => '|',
2865 OutputTheme::Unicode => '│',
2866 }
2867 }
2868
2869 fn note_separator(&self, is_cont: bool) -> &'static str {
2870 match self.theme {
2871 OutputTheme::Ascii => "= ",
2872 OutputTheme::Unicode if is_cont => "├ ",
2873 OutputTheme::Unicode => "╰ ",
2874 }
2875 }
2876
2877 fn multi_suggestion_separator(&self) -> &'static str {
2878 match self.theme {
2879 OutputTheme::Ascii => "|",
2880 OutputTheme::Unicode => "├╴",
2881 }
2882 }
2883
2884 fn draw_col_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2885 let chr = self.col_separator();
2886 buffer.puts(line, col, &format!("{chr} "), Style::LineNumber);
2887 }
2888
2889 fn draw_col_separator_no_space(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2890 let chr = self.col_separator();
2891 self.draw_col_separator_no_space_with_style(buffer, chr, line, col, Style::LineNumber);
2892 }
2893
2894 fn draw_col_separator_start(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2895 match self.theme {
2896 OutputTheme::Ascii => {
2897 self.draw_col_separator_no_space_with_style(
2898 buffer,
2899 '|',
2900 line,
2901 col,
2902 Style::LineNumber,
2903 );
2904 }
2905 OutputTheme::Unicode => {
2906 self.draw_col_separator_no_space_with_style(
2907 buffer,
2908 '╭',
2909 line,
2910 col,
2911 Style::LineNumber,
2912 );
2913 self.draw_col_separator_no_space_with_style(
2914 buffer,
2915 '╴',
2916 line,
2917 col + 1,
2918 Style::LineNumber,
2919 );
2920 }
2921 }
2922 }
2923
2924 fn draw_col_separator_end(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2925 match self.theme {
2926 OutputTheme::Ascii => {
2927 self.draw_col_separator_no_space_with_style(
2928 buffer,
2929 '|',
2930 line,
2931 col,
2932 Style::LineNumber,
2933 );
2934 }
2935 OutputTheme::Unicode => {
2936 self.draw_col_separator_no_space_with_style(
2937 buffer,
2938 '╰',
2939 line,
2940 col,
2941 Style::LineNumber,
2942 );
2943 self.draw_col_separator_no_space_with_style(
2944 buffer,
2945 '╴',
2946 line,
2947 col + 1,
2948 Style::LineNumber,
2949 );
2950 }
2951 }
2952 }
2953
2954 fn draw_col_separator_no_space_with_style(
2955 &self,
2956 buffer: &mut StyledBuffer,
2957 chr: char,
2958 line: usize,
2959 col: usize,
2960 style: Style,
2961 ) {
2962 buffer.putc(line, col, chr, style);
2963 }
2964
2965 fn draw_range(
2966 &self,
2967 buffer: &mut StyledBuffer,
2968 symbol: char,
2969 line: usize,
2970 col_from: usize,
2971 col_to: usize,
2972 style: Style,
2973 ) {
2974 for col in col_from..col_to {
2975 buffer.putc(line, col, symbol, style);
2976 }
2977 }
2978
2979 fn draw_note_separator(
2980 &self,
2981 buffer: &mut StyledBuffer,
2982 line: usize,
2983 col: usize,
2984 is_cont: bool,
2985 ) {
2986 let chr = self.note_separator(is_cont);
2987 buffer.puts(line, col, chr, Style::LineNumber);
2988 }
2989
2990 fn draw_multiline_line(
2991 &self,
2992 buffer: &mut StyledBuffer,
2993 line: usize,
2994 offset: usize,
2995 depth: usize,
2996 style: Style,
2997 ) {
2998 let chr = match (style, self.theme) {
2999 (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Ascii) => '|',
3000 (_, OutputTheme::Ascii) => '|',
3001 (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Unicode) => '┃',
3002 (_, OutputTheme::Unicode) => '│',
3003 };
3004 buffer.putc(line, offset + depth - 1, chr, style);
3005 }
3006
3007 fn file_start(&self) -> &'static str {
3008 match self.theme {
3009 OutputTheme::Ascii => "--> ",
3010 OutputTheme::Unicode => " ╭▸ ",
3011 }
3012 }
3013
3014 fn secondary_file_start(&self) -> &'static str {
3015 match self.theme {
3016 OutputTheme::Ascii => "::: ",
3017 OutputTheme::Unicode => " ⸬ ",
3018 }
3019 }
3020
3021 fn diff(&self) -> char {
3022 match self.theme {
3023 OutputTheme::Ascii => '~',
3024 OutputTheme::Unicode => '±',
3025 }
3026 }
3027
3028 fn draw_line_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
3029 let (column, dots) = match self.theme {
3030 OutputTheme::Ascii => (0, "..."),
3031 OutputTheme::Unicode => (col - 2, "‡"),
3032 };
3033 buffer.puts(line, column, dots, Style::LineNumber);
3034 }
3035
3036 fn margin(&self) -> &'static str {
3037 match self.theme {
3038 OutputTheme::Ascii => "...",
3039 OutputTheme::Unicode => "…",
3040 }
3041 }
3042
3043 fn draw_line_num(
3044 &self,
3045 buffer: &mut StyledBuffer,
3046 line_num: usize,
3047 line_offset: usize,
3048 max_line_num_len: usize,
3049 ) {
3050 let line_num = self.maybe_anonymized(line_num);
3051 buffer.puts(
3052 line_offset,
3053 max_line_num_len.saturating_sub(str_width(&line_num)),
3054 &line_num,
3055 Style::LineNumber,
3056 );
3057 }
3058}
3059
3060#[derive(Debug, Clone, Copy)]
3061struct UnderlineParts {
3062 style: Style,
3063 underline: char,
3064 label_start: char,
3065 vertical_text_line: char,
3066 multiline_vertical: char,
3067 multiline_horizontal: char,
3068 multiline_whole_line: char,
3069 multiline_start_down: char,
3070 bottom_right: char,
3071 top_left: char,
3072 top_right_flat: char,
3073 bottom_left: char,
3074 multiline_end_up: char,
3075 multiline_end_same_line: char,
3076 multiline_bottom_right_with_text: char,
3077}
3078
3079#[derive(Clone, Copy, Debug)]
3080enum DisplaySuggestion {
3081 Underline,
3082 Diff,
3083 None,
3084 Add,
3085}
3086
3087#[derive(Clone, Copy, Debug)]
3088enum CodeWindowStatus {
3089 Closed,
3090 Open,
3091}
3092
3093impl FileWithAnnotatedLines {
3094 pub(crate) fn collect_annotations(
3097 emitter: &dyn Emitter,
3098 args: &FluentArgs<'_>,
3099 msp: &MultiSpan,
3100 ) -> Vec<FileWithAnnotatedLines> {
3101 fn add_annotation_to_file(
3102 file_vec: &mut Vec<FileWithAnnotatedLines>,
3103 file: Arc<SourceFile>,
3104 line_index: usize,
3105 ann: Annotation,
3106 ) {
3107 for slot in file_vec.iter_mut() {
3108 if slot.file.name == file.name {
3110 for line_slot in &mut slot.lines {
3112 if line_slot.line_index == line_index {
3113 line_slot.annotations.push(ann);
3114 return;
3115 }
3116 }
3117 slot.lines.push(Line { line_index, annotations: vec![ann] });
3119 slot.lines.sort();
3120 return;
3121 }
3122 }
3123 file_vec.push(FileWithAnnotatedLines {
3125 file,
3126 lines: vec![Line { line_index, annotations: vec![ann] }],
3127 multiline_depth: 0,
3128 });
3129 }
3130
3131 let mut output = vec![];
3132 let mut multiline_annotations = vec![];
3133
3134 if let Some(sm) = emitter.source_map() {
3135 for SpanLabel { span, is_primary, label } in msp.span_labels() {
3136 let span = match (span.is_dummy(), msp.primary_span()) {
3139 (_, None) | (false, _) => span,
3140 (true, Some(span)) => span,
3141 };
3142
3143 let lo = sm.lookup_char_pos(span.lo());
3144 let mut hi = sm.lookup_char_pos(span.hi());
3145
3146 if lo.col_display == hi.col_display && lo.line == hi.line {
3153 hi.col_display += 1;
3154 }
3155
3156 let label = label.as_ref().map(|m| {
3157 normalize_whitespace(
3158 &emitter
3159 .translator()
3160 .translate_message(m, args)
3161 .map_err(Report::new)
3162 .unwrap(),
3163 )
3164 });
3165
3166 if lo.line != hi.line {
3167 let ml = MultilineAnnotation {
3168 depth: 1,
3169 line_start: lo.line,
3170 line_end: hi.line,
3171 start_col: AnnotationColumn::from_loc(&lo),
3172 end_col: AnnotationColumn::from_loc(&hi),
3173 is_primary,
3174 label,
3175 overlaps_exactly: false,
3176 };
3177 multiline_annotations.push((lo.file, ml));
3178 } else {
3179 let ann = Annotation {
3180 start_col: AnnotationColumn::from_loc(&lo),
3181 end_col: AnnotationColumn::from_loc(&hi),
3182 is_primary,
3183 label,
3184 annotation_type: AnnotationType::Singleline,
3185 };
3186 add_annotation_to_file(&mut output, lo.file, lo.line, ann);
3187 };
3188 }
3189 }
3190
3191 multiline_annotations.sort_by_key(|(_, ml)| (ml.line_start, usize::MAX - ml.line_end));
3193 for (_, ann) in multiline_annotations.clone() {
3194 for (_, a) in multiline_annotations.iter_mut() {
3195 if !(ann.same_span(a))
3198 && num_overlap(ann.line_start, ann.line_end, a.line_start, a.line_end, true)
3199 {
3200 a.increase_depth();
3201 } else if ann.same_span(a) && &ann != a {
3202 a.overlaps_exactly = true;
3203 } else {
3204 break;
3205 }
3206 }
3207 }
3208
3209 let mut max_depth = 0; for (_, ann) in &multiline_annotations {
3211 max_depth = max(max_depth, ann.depth);
3212 }
3213 for (_, a) in multiline_annotations.iter_mut() {
3215 a.depth = max_depth - a.depth + 1;
3216 }
3217 for (file, ann) in multiline_annotations {
3218 let mut end_ann = ann.as_end();
3219 if !ann.overlaps_exactly {
3220 add_annotation_to_file(
3243 &mut output,
3244 Arc::clone(&file),
3245 ann.line_start,
3246 ann.as_start(),
3247 );
3248 let middle = min(ann.line_start + 4, ann.line_end);
3253 let filter = |s: &str| {
3257 let s = s.trim();
3258 !(s.starts_with("//") && !(s.starts_with("///") || s.starts_with("//!")))
3260 && !["", "{", "}", "(", ")", "[", "]"].contains(&s)
3262 };
3263 let until = (ann.line_start..middle)
3264 .rev()
3265 .filter_map(|line| file.get_line(line - 1).map(|s| (line + 1, s)))
3266 .find(|(_, s)| filter(s))
3267 .map(|(line, _)| line)
3268 .unwrap_or(ann.line_start);
3269 for line in ann.line_start + 1..until {
3270 add_annotation_to_file(&mut output, Arc::clone(&file), line, ann.as_line());
3272 }
3273 let line_end = ann.line_end - 1;
3274 let end_is_empty = file.get_line(line_end - 1).is_some_and(|s| !filter(&s));
3275 if middle < line_end && !end_is_empty {
3276 add_annotation_to_file(&mut output, Arc::clone(&file), line_end, ann.as_line());
3277 }
3278 } else {
3279 end_ann.annotation_type = AnnotationType::Singleline;
3280 }
3281 add_annotation_to_file(&mut output, file, ann.line_end, end_ann);
3282 }
3283 for file_vec in output.iter_mut() {
3284 file_vec.multiline_depth = max_depth;
3285 }
3286 output
3287 }
3288}
3289
3290fn num_decimal_digits(num: usize) -> usize {
3295 #[cfg(target_pointer_width = "64")]
3296 const MAX_DIGITS: usize = 20;
3297
3298 #[cfg(target_pointer_width = "32")]
3299 const MAX_DIGITS: usize = 10;
3300
3301 #[cfg(target_pointer_width = "16")]
3302 const MAX_DIGITS: usize = 5;
3303
3304 let mut lim = 10;
3305 for num_digits in 1..MAX_DIGITS {
3306 if num < lim {
3307 return num_digits;
3308 }
3309 lim = lim.wrapping_mul(10);
3310 }
3311 MAX_DIGITS
3312}
3313
3314const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
3317 ('\0', "␀"),
3321 ('\u{0001}', "␁"),
3322 ('\u{0002}', "␂"),
3323 ('\u{0003}', "␃"),
3324 ('\u{0004}', "␄"),
3325 ('\u{0005}', "␅"),
3326 ('\u{0006}', "␆"),
3327 ('\u{0007}', "␇"),
3328 ('\u{0008}', "␈"),
3329 ('\t', " "), ('\u{000b}', "␋"),
3331 ('\u{000c}', "␌"),
3332 ('\u{000d}', "␍"),
3333 ('\u{000e}', "␎"),
3334 ('\u{000f}', "␏"),
3335 ('\u{0010}', "␐"),
3336 ('\u{0011}', "␑"),
3337 ('\u{0012}', "␒"),
3338 ('\u{0013}', "␓"),
3339 ('\u{0014}', "␔"),
3340 ('\u{0015}', "␕"),
3341 ('\u{0016}', "␖"),
3342 ('\u{0017}', "␗"),
3343 ('\u{0018}', "␘"),
3344 ('\u{0019}', "␙"),
3345 ('\u{001a}', "␚"),
3346 ('\u{001b}', "␛"),
3347 ('\u{001c}', "␜"),
3348 ('\u{001d}', "␝"),
3349 ('\u{001e}', "␞"),
3350 ('\u{001f}', "␟"),
3351 ('\u{007f}', "␡"),
3352 ('\u{200d}', ""), ('\u{202a}', "�"), ('\u{202b}', "�"), ('\u{202c}', "�"), ('\u{202d}', "�"),
3357 ('\u{202e}', "�"),
3358 ('\u{2066}', "�"),
3359 ('\u{2067}', "�"),
3360 ('\u{2068}', "�"),
3361 ('\u{2069}', "�"),
3362];
3363
3364fn normalize_whitespace(s: &str) -> String {
3365 const {
3366 let mut i = 1;
3367 while i < OUTPUT_REPLACEMENTS.len() {
3368 assert!(
3369 OUTPUT_REPLACEMENTS[i - 1].0 < OUTPUT_REPLACEMENTS[i].0,
3370 "The OUTPUT_REPLACEMENTS array must be sorted (for binary search to work) \
3371 and must contain no duplicate entries"
3372 );
3373 i += 1;
3374 }
3375 }
3376 s.chars().fold(String::with_capacity(s.len()), |mut s, c| {
3380 match OUTPUT_REPLACEMENTS.binary_search_by_key(&c, |(k, _)| *k) {
3381 Ok(i) => s.push_str(OUTPUT_REPLACEMENTS[i].1),
3382 _ => s.push(c),
3383 }
3384 s
3385 })
3386}
3387
3388fn num_overlap(
3389 a_start: usize,
3390 a_end: usize,
3391 b_start: usize,
3392 b_end: usize,
3393 inclusive: bool,
3394) -> bool {
3395 let extra = if inclusive { 1 } else { 0 };
3396 (b_start..b_end + extra).contains(&a_start) || (a_start..a_end + extra).contains(&b_start)
3397}
3398
3399fn overlaps(a1: &Annotation, a2: &Annotation, padding: usize) -> bool {
3400 num_overlap(
3401 a1.start_col.display,
3402 a1.end_col.display + padding,
3403 a2.start_col.display,
3404 a2.end_col.display,
3405 false,
3406 )
3407}
3408
3409fn emit_to_destination(
3410 rendered_buffer: &[Vec<StyledString>],
3411 lvl: &Level,
3412 dst: &mut Destination,
3413 short_message: bool,
3414) -> io::Result<()> {
3415 use crate::lock;
3416
3417 let _buffer_lock = lock::acquire_global_lock("rustc_errors");
3430 for (pos, line) in rendered_buffer.iter().enumerate() {
3431 for part in line {
3432 let style = part.style.color_spec(*lvl);
3433 dst.set_color(&style)?;
3434 write!(dst, "{}", part.text)?;
3435 dst.reset()?;
3436 }
3437 if !short_message && (!lvl.is_failure_note() || pos != rendered_buffer.len() - 1) {
3438 writeln!(dst)?;
3439 }
3440 }
3441 dst.flush()?;
3442 Ok(())
3443}
3444
3445pub type Destination = Box<dyn WriteColor + Send>;
3446
3447struct Buffy {
3448 buffer_writer: BufferWriter,
3449 buffer: Buffer,
3450}
3451
3452impl Write for Buffy {
3453 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
3454 self.buffer.write(buf)
3455 }
3456
3457 fn flush(&mut self) -> io::Result<()> {
3458 self.buffer_writer.print(&self.buffer)?;
3459 self.buffer.clear();
3460 Ok(())
3461 }
3462}
3463
3464impl Drop for Buffy {
3465 fn drop(&mut self) {
3466 if !self.buffer.is_empty() {
3467 self.flush().unwrap();
3468 panic!("buffers need to be flushed in order to print their contents");
3469 }
3470 }
3471}
3472
3473impl WriteColor for Buffy {
3474 fn supports_color(&self) -> bool {
3475 self.buffer.supports_color()
3476 }
3477
3478 fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
3479 self.buffer.set_color(spec)
3480 }
3481
3482 fn reset(&mut self) -> io::Result<()> {
3483 self.buffer.reset()
3484 }
3485}
3486
3487pub fn stderr_destination(color: ColorConfig) -> Destination {
3488 let choice = color.to_color_choice();
3489 if cfg!(windows) {
3496 Box::new(StandardStream::stderr(choice))
3497 } else {
3498 let buffer_writer = BufferWriter::stderr(choice);
3499 let buffer = buffer_writer.buffer();
3500 Box::new(Buffy { buffer_writer, buffer })
3501 }
3502}
3503
3504const BRIGHT_BLUE: Color = if cfg!(windows) { Color::Cyan } else { Color::Blue };
3508
3509impl Style {
3510 fn color_spec(&self, lvl: Level) -> ColorSpec {
3511 let mut spec = ColorSpec::new();
3512 match self {
3513 Style::Addition => {
3514 spec.set_fg(Some(Color::Green)).set_intense(true);
3515 }
3516 Style::Removal => {
3517 spec.set_fg(Some(Color::Red)).set_intense(true);
3518 }
3519 Style::LineAndColumn => {}
3520 Style::LineNumber => {
3521 spec.set_bold(true);
3522 spec.set_intense(true);
3523 spec.set_fg(Some(BRIGHT_BLUE));
3524 }
3525 Style::Quotation => {}
3526 Style::MainHeaderMsg => {
3527 spec.set_bold(true);
3528 if cfg!(windows) {
3529 spec.set_intense(true).set_fg(Some(Color::White));
3530 }
3531 }
3532 Style::UnderlinePrimary | Style::LabelPrimary => {
3533 spec = lvl.color();
3534 spec.set_bold(true);
3535 }
3536 Style::UnderlineSecondary | Style::LabelSecondary => {
3537 spec.set_bold(true).set_intense(true);
3538 spec.set_fg(Some(BRIGHT_BLUE));
3539 }
3540 Style::HeaderMsg | Style::NoStyle => {}
3541 Style::Level(lvl) => {
3542 spec = lvl.color();
3543 spec.set_bold(true);
3544 }
3545 Style::Highlight => {
3546 spec.set_bold(true).set_fg(Some(Color::Magenta));
3547 }
3548 }
3549 spec
3550 }
3551}
3552
3553pub fn is_different(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
3555 let found = match sm.span_to_snippet(sp) {
3556 Ok(snippet) => snippet,
3557 Err(e) => {
3558 warn!(error = ?e, "Invalid span {:?}", sp);
3559 return true;
3560 }
3561 };
3562 found != suggested
3563}
3564
3565pub fn detect_confusion_type(sm: &SourceMap, suggested: &str, sp: Span) -> ConfusionType {
3567 let found = match sm.span_to_snippet(sp) {
3568 Ok(snippet) => snippet,
3569 Err(e) => {
3570 warn!(error = ?e, "Invalid span {:?}", sp);
3571 return ConfusionType::None;
3572 }
3573 };
3574
3575 let mut has_case_confusion = false;
3576 let mut has_digit_letter_confusion = false;
3577
3578 if found.len() == suggested.len() {
3579 let mut has_case_diff = false;
3580 let mut has_digit_letter_confusable = false;
3581 let mut has_other_diff = false;
3582
3583 let ascii_confusables = &['c', 'f', 'i', 'k', 'o', 's', 'u', 'v', 'w', 'x', 'y', 'z'];
3584
3585 let digit_letter_confusables = [('0', 'O'), ('1', 'l'), ('5', 'S'), ('8', 'B'), ('9', 'g')];
3586
3587 for (f, s) in iter::zip(found.chars(), suggested.chars()) {
3588 if f != s {
3589 if f.eq_ignore_ascii_case(&s) {
3590 if ascii_confusables.contains(&f) || ascii_confusables.contains(&s) {
3592 has_case_diff = true;
3593 } else {
3594 has_other_diff = true;
3595 }
3596 } else if digit_letter_confusables.contains(&(f, s))
3597 || digit_letter_confusables.contains(&(s, f))
3598 {
3599 has_digit_letter_confusable = true;
3601 } else {
3602 has_other_diff = true;
3603 }
3604 }
3605 }
3606
3607 if has_case_diff && !has_other_diff && found != suggested {
3609 has_case_confusion = true;
3610 }
3611 if has_digit_letter_confusable && !has_other_diff && found != suggested {
3612 has_digit_letter_confusion = true;
3613 }
3614 }
3615
3616 match (has_case_confusion, has_digit_letter_confusion) {
3617 (true, true) => ConfusionType::Both,
3618 (true, false) => ConfusionType::Case,
3619 (false, true) => ConfusionType::DigitLetter,
3620 (false, false) => ConfusionType::None,
3621 }
3622}
3623
3624#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3626pub enum ConfusionType {
3627 None,
3629 Case,
3631 DigitLetter,
3633 Both,
3635}
3636
3637impl ConfusionType {
3638 pub fn label_text(&self) -> &'static str {
3640 match self {
3641 ConfusionType::None => "",
3642 ConfusionType::Case => " (notice the capitalization)",
3643 ConfusionType::DigitLetter => " (notice the digit/letter confusion)",
3644 ConfusionType::Both => " (notice the capitalization and digit/letter confusion)",
3645 }
3646 }
3647
3648 pub fn combine(self, other: ConfusionType) -> ConfusionType {
3652 match (self, other) {
3653 (ConfusionType::None, other) => other,
3654 (this, ConfusionType::None) => this,
3655 (ConfusionType::Both, _) | (_, ConfusionType::Both) => ConfusionType::Both,
3656 (ConfusionType::Case, ConfusionType::DigitLetter)
3657 | (ConfusionType::DigitLetter, ConfusionType::Case) => ConfusionType::Both,
3658 (ConfusionType::Case, ConfusionType::Case) => ConfusionType::Case,
3659 (ConfusionType::DigitLetter, ConfusionType::DigitLetter) => ConfusionType::DigitLetter,
3660 }
3661 }
3662
3663 pub fn has_confusion(&self) -> bool {
3665 *self != ConfusionType::None
3666 }
3667}
3668
3669pub(crate) fn should_show_source_code(
3670 ignored_directories: &[String],
3671 sm: &SourceMap,
3672 file: &SourceFile,
3673) -> bool {
3674 if !sm.ensure_source_file_source_present(file) {
3675 return false;
3676 }
3677
3678 let FileName::Real(name) = &file.name else { return true };
3679 name.local_path()
3680 .map(|path| ignored_directories.iter().all(|dir| !path.starts_with(dir)))
3681 .unwrap_or(true)
3682}