rustc_errors/
emitter.rs

1//! The current rustc diagnostics emitter.
2//!
3//! An `Emitter` takes care of generating the output from a `Diag` struct.
4//!
5//! There are various `Emitter` implementations that generate different output formats such as
6//! JSON and human readable output.
7//!
8//! The output types are defined in `rustc_session::config::ErrorOutputType`.
9
10use 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::{FxHashMap, 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::diagnostic::DiagLocation;
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::{Translate, to_fluent_args};
39use crate::{
40    CodeSuggestion, DiagInner, DiagMessage, ErrCode, FluentBundle, LazyFallbackBundle, Level,
41    MultiSpan, Subdiag, SubstitutionHighlight, SuggestionStyle, TerminalUrl,
42};
43
44/// Default column width, used in tests and when terminal dimensions cannot be determined.
45const DEFAULT_COLUMN_WIDTH: usize = 140;
46
47/// Describes the way the content of the `rendered` field of the json output is generated
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub enum HumanReadableErrorType {
50    Default,
51    Unicode,
52    AnnotateSnippet,
53    Short,
54}
55
56impl HumanReadableErrorType {
57    pub fn short(&self) -> bool {
58        *self == HumanReadableErrorType::Short
59    }
60}
61
62#[derive(Clone, Copy, Debug)]
63struct Margin {
64    /// The available whitespace in the left that can be consumed when centering.
65    pub whitespace_left: usize,
66    /// The column of the beginning of leftmost span.
67    pub span_left: usize,
68    /// The column of the end of rightmost span.
69    pub span_right: usize,
70    /// The beginning of the line to be displayed.
71    pub computed_left: usize,
72    /// The end of the line to be displayed.
73    pub computed_right: usize,
74    /// The current width of the terminal. Uses value of `DEFAULT_COLUMN_WIDTH` constant by default
75    /// and in tests.
76    pub column_width: usize,
77    /// The end column of a span label, including the span. Doesn't account for labels not in the
78    /// same line as the span.
79    pub label_right: usize,
80}
81
82impl Margin {
83    fn new(
84        whitespace_left: usize,
85        span_left: usize,
86        span_right: usize,
87        label_right: usize,
88        column_width: usize,
89        max_line_len: usize,
90    ) -> Self {
91        // The 6 is padding to give a bit of room for `...` when displaying:
92        // ```
93        // error: message
94        //   --> file.rs:16:58
95        //    |
96        // 16 | ... fn foo(self) -> Self::Bar {
97        //    |                     ^^^^^^^^^
98        // ```
99
100        let mut m = Margin {
101            whitespace_left: whitespace_left.saturating_sub(6),
102            span_left: span_left.saturating_sub(6),
103            span_right: span_right + 6,
104            computed_left: 0,
105            computed_right: 0,
106            column_width,
107            label_right: label_right + 6,
108        };
109        m.compute(max_line_len);
110        m
111    }
112
113    fn was_cut_left(&self) -> bool {
114        self.computed_left > 0
115    }
116
117    fn compute(&mut self, max_line_len: usize) {
118        // When there's a lot of whitespace (>20), we want to trim it as it is useless.
119        // FIXME: this doesn't account for '\t', but to do so correctly we need to perform that
120        // calculation later, right before printing in order to be accurate with both unicode
121        // handling and trimming of long lines.
122        self.computed_left = if self.whitespace_left > 20 {
123            self.whitespace_left - 16 // We want some padding.
124        } else {
125            0
126        };
127        // We want to show as much as possible, max_line_len is the rightmost boundary for the
128        // relevant code.
129        self.computed_right = max(max_line_len, self.computed_left);
130
131        if self.computed_right - self.computed_left > self.column_width {
132            // Trimming only whitespace isn't enough, let's get craftier.
133            if self.label_right - self.whitespace_left <= self.column_width {
134                // Attempt to fit the code window only trimming whitespace.
135                self.computed_left = self.whitespace_left;
136                self.computed_right = self.computed_left + self.column_width;
137            } else if self.label_right - self.span_left <= self.column_width {
138                // Attempt to fit the code window considering only the spans and labels.
139                let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2;
140                self.computed_left = self.span_left.saturating_sub(padding_left);
141                self.computed_right = self.computed_left + self.column_width;
142            } else if self.span_right - self.span_left <= self.column_width {
143                // Attempt to fit the code window considering the spans and labels plus padding.
144                let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2;
145                self.computed_left = self.span_left.saturating_sub(padding_left);
146                self.computed_right = self.computed_left + self.column_width;
147            } else {
148                // Mostly give up but still don't show the full line.
149                self.computed_left = self.span_left;
150                self.computed_right = self.span_right;
151            }
152        }
153    }
154
155    fn left(&self, line_len: usize) -> usize {
156        min(self.computed_left, line_len)
157    }
158
159    fn right(&self, line_len: usize) -> usize {
160        if line_len.saturating_sub(self.computed_left) <= self.column_width {
161            line_len
162        } else {
163            min(line_len, self.computed_right)
164        }
165    }
166}
167
168pub enum TimingEvent {
169    Start,
170    End,
171}
172
173const ANONYMIZED_LINE_NUM: &str = "LL";
174
175pub type DynEmitter = dyn Emitter + DynSend;
176
177/// Emitter trait for emitting errors and other structured information.
178pub trait Emitter: Translate {
179    /// Emit a structured diagnostic.
180    fn emit_diagnostic(&mut self, diag: DiagInner, registry: &Registry);
181
182    /// Emit a notification that an artifact has been output.
183    /// Currently only supported for the JSON format.
184    fn emit_artifact_notification(&mut self, _path: &Path, _artifact_type: &str) {}
185
186    /// Emit a timestamp with start/end of a timing section.
187    /// Currently only supported for the JSON format.
188    fn emit_timing_section(&mut self, _record: TimingRecord, _event: TimingEvent) {}
189
190    /// Emit a report about future breakage.
191    /// Currently only supported for the JSON format.
192    fn emit_future_breakage_report(&mut self, _diags: Vec<DiagInner>, _registry: &Registry) {}
193
194    /// Emit list of unused externs.
195    /// Currently only supported for the JSON format.
196    fn emit_unused_externs(
197        &mut self,
198        _lint_level: rustc_lint_defs::Level,
199        _unused_externs: &[&str],
200    ) {
201    }
202
203    /// Checks if should show explanations about "rustc --explain"
204    fn should_show_explain(&self) -> bool {
205        true
206    }
207
208    /// Checks if we can use colors in the current output stream.
209    fn supports_color(&self) -> bool {
210        false
211    }
212
213    fn source_map(&self) -> Option<&SourceMap>;
214
215    /// Formats the substitutions of the primary_span
216    ///
217    /// There are a lot of conditions to this method, but in short:
218    ///
219    /// * If the current `DiagInner` has only one visible `CodeSuggestion`,
220    ///   we format the `help` suggestion depending on the content of the
221    ///   substitutions. In that case, we modify the span and clear the
222    ///   suggestions.
223    ///
224    /// * If the current `DiagInner` has multiple suggestions,
225    ///   we leave `primary_span` and the suggestions untouched.
226    fn primary_span_formatted(
227        &mut self,
228        primary_span: &mut MultiSpan,
229        suggestions: &mut Vec<CodeSuggestion>,
230        fluent_args: &FluentArgs<'_>,
231    ) {
232        if let Some((sugg, rest)) = suggestions.split_first() {
233            let msg = self.translate_message(&sugg.msg, fluent_args).map_err(Report::new).unwrap();
234            if rest.is_empty()
235               // ^ if there is only one suggestion
236               // don't display multi-suggestions as labels
237               && let [substitution] = sugg.substitutions.as_slice()
238               // don't display multipart suggestions as labels
239               && let [part] = substitution.parts.as_slice()
240               // don't display long messages as labels
241               && msg.split_whitespace().count() < 10
242               // don't display multiline suggestions as labels
243               && !part.snippet.contains('\n')
244               && ![
245                    // when this style is set we want the suggestion to be a message, not inline
246                    SuggestionStyle::HideCodeAlways,
247                    // trivial suggestion for tooling's sake, never shown
248                    SuggestionStyle::CompletelyHidden,
249                    // subtle suggestion, never shown inline
250                    SuggestionStyle::ShowAlways,
251               ].contains(&sugg.style)
252            {
253                let snippet = part.snippet.trim();
254                let msg = if snippet.is_empty() || sugg.style.hide_inline() {
255                    // This substitution is only removal OR we explicitly don't want to show the
256                    // code inline (`hide_inline`). Therefore, we don't show the substitution.
257                    format!("help: {msg}")
258                } else {
259                    // Show the default suggestion text with the substitution
260                    format!(
261                        "help: {}{}: `{}`",
262                        msg,
263                        if self
264                            .source_map()
265                            .is_some_and(|sm| is_case_difference(sm, snippet, part.span,))
266                        {
267                            " (notice the capitalization)"
268                        } else {
269                            ""
270                        },
271                        snippet,
272                    )
273                };
274                primary_span.push_span_label(part.span, msg);
275
276                // We return only the modified primary_span
277                suggestions.clear();
278            } else {
279                // if there are multiple suggestions, print them all in full
280                // to be consistent. We could try to figure out if we can
281                // make one (or the first one) inline, but that would give
282                // undue importance to a semi-random suggestion
283            }
284        } else {
285            // do nothing
286        }
287    }
288
289    fn fix_multispans_in_extern_macros_and_render_macro_backtrace(
290        &self,
291        span: &mut MultiSpan,
292        children: &mut Vec<Subdiag>,
293        level: &Level,
294        backtrace: bool,
295    ) {
296        // Check for spans in macros, before `fix_multispans_in_extern_macros`
297        // has a chance to replace them.
298        let has_macro_spans: Vec<_> = iter::once(&*span)
299            .chain(children.iter().map(|child| &child.span))
300            .flat_map(|span| span.primary_spans())
301            .flat_map(|sp| sp.macro_backtrace())
302            .filter_map(|expn_data| {
303                match expn_data.kind {
304                    ExpnKind::Root => None,
305
306                    // Skip past non-macro entries, just in case there
307                    // are some which do actually involve macros.
308                    ExpnKind::Desugaring(..) | ExpnKind::AstPass(..) => None,
309
310                    ExpnKind::Macro(macro_kind, name) => {
311                        Some((macro_kind, name, expn_data.hide_backtrace))
312                    }
313                }
314            })
315            .collect();
316
317        if !backtrace {
318            self.fix_multispans_in_extern_macros(span, children);
319        }
320
321        self.render_multispans_macro_backtrace(span, children, backtrace);
322
323        if !backtrace {
324            // Skip builtin macros, as their expansion isn't relevant to the end user. This includes
325            // actual intrinsics, like `asm!`.
326            if let Some((macro_kind, name, _)) = has_macro_spans.first()
327                && let Some((_, _, false)) = has_macro_spans.last()
328            {
329                // Mark the actual macro this originates from
330                let and_then = if let Some((macro_kind, last_name, _)) = has_macro_spans.last()
331                    && last_name != name
332                {
333                    let descr = macro_kind.descr();
334                    format!(" which comes from the expansion of the {descr} `{last_name}`")
335                } else {
336                    "".to_string()
337                };
338
339                let descr = macro_kind.descr();
340                let msg = format!(
341                    "this {level} originates in the {descr} `{name}`{and_then} \
342                    (in Nightly builds, run with -Z macro-backtrace for more info)",
343                );
344
345                children.push(Subdiag {
346                    level: Level::Note,
347                    messages: vec![(DiagMessage::from(msg), Style::NoStyle)],
348                    span: MultiSpan::new(),
349                });
350            }
351        }
352    }
353
354    fn render_multispans_macro_backtrace(
355        &self,
356        span: &mut MultiSpan,
357        children: &mut Vec<Subdiag>,
358        backtrace: bool,
359    ) {
360        for span in iter::once(span).chain(children.iter_mut().map(|child| &mut child.span)) {
361            self.render_multispan_macro_backtrace(span, backtrace);
362        }
363    }
364
365    fn render_multispan_macro_backtrace(&self, span: &mut MultiSpan, always_backtrace: bool) {
366        let mut new_labels = FxIndexSet::default();
367
368        for &sp in span.primary_spans() {
369            if sp.is_dummy() {
370                continue;
371            }
372
373            // FIXME(eddyb) use `retain` on `macro_backtrace` to remove all the
374            // entries we don't want to print, to make sure the indices being
375            // printed are contiguous (or omitted if there's only one entry).
376            let macro_backtrace: Vec<_> = sp.macro_backtrace().collect();
377            for (i, trace) in macro_backtrace.iter().rev().enumerate() {
378                if trace.def_site.is_dummy() {
379                    continue;
380                }
381
382                if always_backtrace {
383                    new_labels.insert((
384                        trace.def_site,
385                        format!(
386                            "in this expansion of `{}`{}",
387                            trace.kind.descr(),
388                            if macro_backtrace.len() > 1 {
389                                // if macro_backtrace.len() == 1 it'll be
390                                // pointed at by "in this macro invocation"
391                                format!(" (#{})", i + 1)
392                            } else {
393                                String::new()
394                            },
395                        ),
396                    ));
397                }
398
399                // Don't add a label on the call site if the diagnostic itself
400                // already points to (a part of) that call site, as the label
401                // is meant for showing the relevant invocation when the actual
402                // diagnostic is pointing to some part of macro definition.
403                //
404                // This also handles the case where an external span got replaced
405                // with the call site span by `fix_multispans_in_extern_macros`.
406                //
407                // NB: `-Zmacro-backtrace` overrides this, for uniformity, as the
408                // "in this expansion of" label above is always added in that mode,
409                // and it needs an "in this macro invocation" label to match that.
410                let redundant_span = trace.call_site.contains(sp);
411
412                if !redundant_span || always_backtrace {
413                    let msg: Cow<'static, _> = match trace.kind {
414                        ExpnKind::Macro(MacroKind::Attr, _) => {
415                            "this procedural macro expansion".into()
416                        }
417                        ExpnKind::Macro(MacroKind::Derive, _) => {
418                            "this derive macro expansion".into()
419                        }
420                        ExpnKind::Macro(MacroKind::Bang, _) => "this macro invocation".into(),
421                        ExpnKind::Root => "the crate root".into(),
422                        ExpnKind::AstPass(kind) => kind.descr().into(),
423                        ExpnKind::Desugaring(kind) => {
424                            format!("this {} desugaring", kind.descr()).into()
425                        }
426                    };
427                    new_labels.insert((
428                        trace.call_site,
429                        format!(
430                            "in {}{}",
431                            msg,
432                            if macro_backtrace.len() > 1 && always_backtrace {
433                                // only specify order when the macro
434                                // backtrace is multiple levels deep
435                                format!(" (#{})", i + 1)
436                            } else {
437                                String::new()
438                            },
439                        ),
440                    ));
441                }
442                if !always_backtrace {
443                    break;
444                }
445            }
446        }
447
448        for (label_span, label_text) in new_labels {
449            span.push_span_label(label_span, label_text);
450        }
451    }
452
453    // This does a small "fix" for multispans by looking to see if it can find any that
454    // point directly at external macros. Since these are often difficult to read,
455    // this will change the span to point at the use site.
456    fn fix_multispans_in_extern_macros(&self, span: &mut MultiSpan, children: &mut Vec<Subdiag>) {
457        debug!("fix_multispans_in_extern_macros: before: span={:?} children={:?}", span, children);
458        self.fix_multispan_in_extern_macros(span);
459        for child in children.iter_mut() {
460            self.fix_multispan_in_extern_macros(&mut child.span);
461        }
462        debug!("fix_multispans_in_extern_macros: after: span={:?} children={:?}", span, children);
463    }
464
465    // This "fixes" MultiSpans that contain `Span`s pointing to locations inside of external macros.
466    // Since these locations are often difficult to read,
467    // we move these spans from the external macros to their corresponding use site.
468    fn fix_multispan_in_extern_macros(&self, span: &mut MultiSpan) {
469        let Some(source_map) = self.source_map() else { return };
470        // First, find all the spans in external macros and point instead at their use site.
471        let replacements: Vec<(Span, Span)> = span
472            .primary_spans()
473            .iter()
474            .copied()
475            .chain(span.span_labels().iter().map(|sp_label| sp_label.span))
476            .filter_map(|sp| {
477                if !sp.is_dummy() && source_map.is_imported(sp) {
478                    let maybe_callsite = sp.source_callsite();
479                    if sp != maybe_callsite {
480                        return Some((sp, maybe_callsite));
481                    }
482                }
483                None
484            })
485            .collect();
486
487        // After we have them, make sure we replace these 'bad' def sites with their use sites.
488        for (from, to) in replacements {
489            span.replace(from, to);
490        }
491    }
492}
493
494impl Translate for HumanEmitter {
495    fn fluent_bundle(&self) -> Option<&FluentBundle> {
496        self.fluent_bundle.as_deref()
497    }
498
499    fn fallback_fluent_bundle(&self) -> &FluentBundle {
500        &self.fallback_bundle
501    }
502}
503
504impl Emitter for HumanEmitter {
505    fn source_map(&self) -> Option<&SourceMap> {
506        self.sm.as_deref()
507    }
508
509    fn emit_diagnostic(&mut self, mut diag: DiagInner, _registry: &Registry) {
510        let fluent_args = to_fluent_args(diag.args.iter());
511
512        let mut suggestions = diag.suggestions.unwrap_tag();
513        self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args);
514
515        self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
516            &mut diag.span,
517            &mut diag.children,
518            &diag.level,
519            self.macro_backtrace,
520        );
521
522        self.emit_messages_default(
523            &diag.level,
524            &diag.messages,
525            &fluent_args,
526            &diag.code,
527            &diag.span,
528            &diag.children,
529            &suggestions,
530            self.track_diagnostics.then_some(&diag.emitted_at),
531        );
532    }
533
534    fn should_show_explain(&self) -> bool {
535        !self.short_message
536    }
537
538    fn supports_color(&self) -> bool {
539        self.dst.supports_color()
540    }
541}
542
543/// An emitter that does nothing when emitting a non-fatal diagnostic.
544/// Fatal diagnostics are forwarded to `fatal_emitter` to avoid silent
545/// failures of rustc, as witnessed e.g. in issue #89358.
546pub struct SilentEmitter {
547    pub fatal_emitter: Box<dyn Emitter + DynSend>,
548    pub fatal_note: Option<String>,
549    pub emit_fatal_diagnostic: bool,
550}
551
552impl Translate for SilentEmitter {
553    fn fluent_bundle(&self) -> Option<&FluentBundle> {
554        None
555    }
556
557    fn fallback_fluent_bundle(&self) -> &FluentBundle {
558        self.fatal_emitter.fallback_fluent_bundle()
559    }
560}
561
562impl Emitter for SilentEmitter {
563    fn source_map(&self) -> Option<&SourceMap> {
564        None
565    }
566
567    fn emit_diagnostic(&mut self, mut diag: DiagInner, registry: &Registry) {
568        if self.emit_fatal_diagnostic && diag.level == Level::Fatal {
569            if let Some(fatal_note) = &self.fatal_note {
570                diag.sub(Level::Note, fatal_note.clone(), MultiSpan::new());
571            }
572            self.fatal_emitter.emit_diagnostic(diag, registry);
573        }
574    }
575}
576
577/// Maximum number of suggestions to be shown
578///
579/// Arbitrary, but taken from trait import suggestion limit
580pub const MAX_SUGGESTIONS: usize = 4;
581
582#[derive(Clone, Copy, Debug, PartialEq, Eq)]
583pub enum ColorConfig {
584    Auto,
585    Always,
586    Never,
587}
588
589impl ColorConfig {
590    pub fn to_color_choice(self) -> ColorChoice {
591        match self {
592            ColorConfig::Always => {
593                if io::stderr().is_terminal() {
594                    ColorChoice::Always
595                } else {
596                    ColorChoice::AlwaysAnsi
597                }
598            }
599            ColorConfig::Never => ColorChoice::Never,
600            ColorConfig::Auto if io::stderr().is_terminal() => ColorChoice::Auto,
601            ColorConfig::Auto => ColorChoice::Never,
602        }
603    }
604}
605
606#[derive(Debug, Clone, Copy, PartialEq, Eq)]
607pub enum OutputTheme {
608    Ascii,
609    Unicode,
610}
611
612/// Handles the writing of `HumanReadableErrorType::Default` and `HumanReadableErrorType::Short`
613#[derive(Setters)]
614pub struct HumanEmitter {
615    #[setters(skip)]
616    dst: IntoDynSyncSend<Destination>,
617    sm: Option<Arc<SourceMap>>,
618    fluent_bundle: Option<Arc<FluentBundle>>,
619    #[setters(skip)]
620    fallback_bundle: LazyFallbackBundle,
621    short_message: bool,
622    ui_testing: bool,
623    ignored_directories_in_source_blocks: Vec<String>,
624    diagnostic_width: Option<usize>,
625
626    macro_backtrace: bool,
627    track_diagnostics: bool,
628    terminal_url: TerminalUrl,
629    theme: OutputTheme,
630}
631
632#[derive(Debug)]
633pub(crate) struct FileWithAnnotatedLines {
634    pub(crate) file: Arc<SourceFile>,
635    pub(crate) lines: Vec<Line>,
636    multiline_depth: usize,
637}
638
639impl HumanEmitter {
640    pub fn new(dst: Destination, fallback_bundle: LazyFallbackBundle) -> HumanEmitter {
641        HumanEmitter {
642            dst: IntoDynSyncSend(dst),
643            sm: None,
644            fluent_bundle: None,
645            fallback_bundle,
646            short_message: false,
647            ui_testing: false,
648            ignored_directories_in_source_blocks: Vec::new(),
649            diagnostic_width: None,
650            macro_backtrace: false,
651            track_diagnostics: false,
652            terminal_url: TerminalUrl::No,
653            theme: OutputTheme::Ascii,
654        }
655    }
656
657    fn maybe_anonymized(&self, line_num: usize) -> Cow<'static, str> {
658        if self.ui_testing {
659            Cow::Borrowed(ANONYMIZED_LINE_NUM)
660        } else {
661            Cow::Owned(line_num.to_string())
662        }
663    }
664
665    fn draw_line(
666        &self,
667        buffer: &mut StyledBuffer,
668        source_string: &str,
669        line_index: usize,
670        line_offset: usize,
671        width_offset: usize,
672        code_offset: usize,
673        margin: Margin,
674    ) -> usize {
675        let line_len = source_string.len();
676        // Create the source line we will highlight.
677        let left = margin.left(line_len);
678        let right = margin.right(line_len);
679        // FIXME: The following code looks fishy. See #132860.
680        // On long lines, we strip the source line, accounting for unicode.
681        let code: String = source_string
682            .chars()
683            .enumerate()
684            .skip_while(|(i, _)| *i < left)
685            .take_while(|(i, _)| *i < right)
686            .map(|(_, c)| c)
687            .collect();
688        let code = normalize_whitespace(&code);
689        let was_cut_right =
690            source_string.chars().enumerate().skip_while(|(i, _)| *i < right).next().is_some();
691        buffer.puts(line_offset, code_offset, &code, Style::Quotation);
692        let placeholder = self.margin();
693        if margin.was_cut_left() {
694            // We have stripped some code/whitespace from the beginning, make it clear.
695            buffer.puts(line_offset, code_offset, placeholder, Style::LineNumber);
696        }
697        if was_cut_right {
698            let padding = str_width(placeholder);
699            // We have stripped some code after the rightmost span end, make it clear we did so.
700            buffer.puts(
701                line_offset,
702                code_offset + str_width(&code) - padding,
703                placeholder,
704                Style::LineNumber,
705            );
706        }
707        buffer.puts(line_offset, 0, &self.maybe_anonymized(line_index), Style::LineNumber);
708
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        // Draw:
725        //
726        //   LL | ... code ...
727        //      |     ^^-^ span label
728        //      |       |
729        //      |       secondary span label
730        //
731        //   ^^ ^ ^^^ ^^^^ ^^^ we don't care about code too far to the right of a span, we trim it
732        //   |  | |   |
733        //   |  | |   actual code found in your source code and the spans we use to mark it
734        //   |  | when there's too much wasted space to the left, trim it
735        //   |  vertical divider between the column number and the code
736        //   column number
737
738        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        // Left trim.
750        // FIXME: This looks fishy. See #132860.
751        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        // Special case when there's only one annotation involved, it is the start of a multiline
762        // span and there's no text at the beginning of the code line. Instead of doing the whole
763        // graph:
764        //
765        // 2 |   fn foo() {
766        //   |  _^
767        // 3 | |
768        // 4 | | }
769        //   | |_^ test
770        //
771        // we simplify the output to:
772        //
773        // 2 | / fn foo() {
774        // 3 | |
775        // 4 | | }
776        //   | |_^ test
777        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        // We want to display like this:
805        //
806        //      vec.push(vec.pop().unwrap());
807        //      ---      ^^^               - previous borrow ends here
808        //      |        |
809        //      |        error occurs here
810        //      previous borrow of `vec` occurs here
811        //
812        // But there are some weird edge cases to be aware of:
813        //
814        //      vec.push(vec.pop().unwrap());
815        //      --------                    - previous borrow ends here
816        //      ||
817        //      |this makes no sense
818        //      previous borrow of `vec` occurs here
819        //
820        // For this reason, we group the lines into "highlight lines"
821        // and "annotations lines", where the highlight lines have the `^`.
822
823        // Sort the annotations by (start, end col)
824        // The labels are reversed, sort and then reversed again.
825        // Consider a list of annotations (A1, A2, C1, C2, B1, B2) where
826        // the letter signifies the span. Here we are only sorting by the
827        // span and hence, the order of the elements with the same span will
828        // not change. On reversing the ordering (|a, b| but b.cmp(a)), you get
829        // (C1, C2, B1, B2, A1, A2). All the elements with the same span are
830        // still ordered first to last, but all the elements with different
831        // spans are ordered by their spans in last to first order. Last to
832        // first order is important, because the jiggly lines and | are on
833        // the left, so the rightmost span needs to be rendered first,
834        // otherwise the lines would end up needing to go over a message.
835
836        let mut annotations = line.annotations.clone();
837        annotations.sort_by_key(|a| Reverse(a.start_col));
838
839        // First, figure out where each label will be positioned.
840        //
841        // In the case where you have the following annotations:
842        //
843        //      vec.push(vec.pop().unwrap());
844        //      --------                    - previous borrow ends here [C]
845        //      ||
846        //      |this makes no sense [B]
847        //      previous borrow of `vec` occurs here [A]
848        //
849        // `annotations_position` will hold [(2, A), (1, B), (0, C)].
850        //
851        // We try, when possible, to stick the rightmost annotation at the end
852        // of the highlight line:
853        //
854        //      vec.push(vec.pop().unwrap());
855        //      ---      ---               - previous borrow ends here
856        //
857        // But sometimes that's not possible because one of the other
858        // annotations overlaps it. For example, from the test
859        // `span_overlap_label`, we have the following annotations
860        // (written on distinct lines for clarity):
861        //
862        //      fn foo(x: u32) {
863        //      --------------
864        //             -
865        //
866        // In this case, we can't stick the rightmost-most label on
867        // the highlight line, or we would get:
868        //
869        //      fn foo(x: u32) {
870        //      -------- x_span
871        //      |
872        //      fn_span
873        //
874        // which is totally weird. Instead we want:
875        //
876        //      fn foo(x: u32) {
877        //      --------------
878        //      |      |
879        //      |      x_span
880        //      fn_span
881        //
882        // which is...less weird, at least. In fact, in general, if
883        // the rightmost span overlaps with any other span, we should
884        // use the "hang below" version, so we can at least make it
885        // clear where the span *starts*. There's an exception for this
886        // logic, when the labels do not have a message:
887        //
888        //      fn foo(x: u32) {
889        //      --------------
890        //             |
891        //             x_span
892        //
893        // instead of:
894        //
895        //      fn foo(x: u32) {
896        //      --------------
897        //      |      |
898        //      |      x_span
899        //      <EMPTY LINE>
900        //
901        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)  // This label overlaps with another one and both
912                    && annotation.has_label()     // take space (they have text and are not
913                    && j > i                      // multiline lines).
914                    && p == 0
915                // We're currently on the first line, move the label one line down
916                {
917                    // If we're overlapping with an un-labelled annotation with the same span
918                    // we can just merge them in the output
919                    if next.start_col == annotation.start_col
920                        && next.end_col == annotation.end_col
921                        && !next.has_label()
922                    {
923                        continue;
924                    }
925
926                    // This annotation needs a new line in the output.
927                    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) // Do not allow two labels to be in the same
936                                                     // line if they overlap including padding, to
937                                                     // avoid situations like:
938                                                     //
939                                                     //      fn foo(x: u32) {
940                                                     //      -------^------
941                                                     //      |      |
942                                                     //      fn_spanx_span
943                                                     //
944                        && annotation.has_label()    // Both labels must have some text, otherwise
945                        && next.has_label())         // they are not overlapping.
946                                                     // Do not add a new line if this annotation
947                                                     // or the next are vertical line placeholders.
948                        || (annotation.takes_space() // If either this or the next annotation is
949                            && next.has_label())     // multiline start/end, move it to a new line
950                        || (annotation.has_label()   // so as not to overlap the horizontal lines.
951                            && 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                    // Avoid #42595.
958                    {
959                        // This annotation needs a new line in the output.
960                        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 there are no annotations or the only annotations on this line are
973        // MultilineLine, then there's only code being shown, stop processing.
974        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            // Special case the following, so that we minimize overlapping multiline spans.
984            //
985            // 3 │       X0 Y0 Z0
986            //   │ ┏━━━━━┛  │  │     < We are writing these lines
987            //   │ ┃┌───────┘  │     < by reverting the "depth" of
988            //   │ ┃│┌─────────┘     < their multiline spans.
989            // 4 │ ┃││   X1 Y1 Z1
990            // 5 │ ┃││   X2 Y2 Z2
991            //   │ ┃│└────╿──│──┘ `Z` label
992            //   │ ┃└─────│──┤
993            //   │ ┗━━━━━━┥  `Y` is a good letter too
994            //   ╰╴       `X` is a good letter
995            for (pos, _) in &mut annotations_position {
996                *pos = max_pos - *pos;
997            }
998            // We know then that we don't need an additional line for the span label, saving us
999            // one line of vertical space.
1000            line_len = line_len.saturating_sub(1);
1001        }
1002
1003        // Write the column separator.
1004        //
1005        // After this we will have:
1006        //
1007        // 2 |   fn foo() {
1008        //   |
1009        //   |
1010        //   |
1011        // 3 |
1012        // 4 |   }
1013        //   |
1014        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        // Write the horizontal lines for multiline annotations
1022        // (only the first and last lines need this).
1023        //
1024        // After this we will have:
1025        //
1026        // 2 |   fn foo() {
1027        //   |  __________
1028        //   |
1029        //   |
1030        // 3 |
1031        // 4 |   }
1032        //   |  _
1033        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        // Write the vertical lines for labels that are on a different line as the underline.
1058        //
1059        // After this we will have:
1060        //
1061        // 2 |   fn foo() {
1062        //   |  __________
1063        //   | |    |
1064        //   | |
1065        // 3 | |
1066        // 4 | | }
1067        //   | |_
1068        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        // Write the labels on the annotations that actually have a label.
1148        //
1149        // After this we will have:
1150        //
1151        // 2 |   fn foo() {
1152        //   |  __________
1153        //   |      |
1154        //   |      something about `foo`
1155        // 3 |
1156        // 4 |   }
1157        //   |  _  test
1158        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        // Sort from biggest span to smallest span so that smaller spans are
1193        // represented in the output:
1194        //
1195        // x | fn foo()
1196        //   | ^^^---^^
1197        //   | |  |
1198        //   | |  something about `foo`
1199        //   | something about `fn foo()`
1200        annotations_position.sort_by_key(|(_, ann)| {
1201            // Decreasing order. When annotations share the same length, prefer `Primary`.
1202            (Reverse(ann.len()), ann.is_primary)
1203        });
1204
1205        // Write the underlines.
1206        //
1207        // After this we will have:
1208        //
1209        // 2 |   fn foo() {
1210        //   |  ____-_____^
1211        //   |      |
1212        //   |      something about `foo`
1213        // 3 |
1214        // 4 |   }
1215        //   |  _^  test
1216        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                // The default span label underline.
1246                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                // The beginning of a multiline span with its leftward moving line on the same line.
1256                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                // The beginning of a multiline span with its leftward moving line on another line,
1273                // so we start going down first.
1274                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                // The beginning of a span label with an actual label, we'll point down.
1286                buffer.putc(line_offset + 1, code_offset, uline.label_start, uline.style);
1287            }
1288        }
1289
1290        // We look for individual *long* spans, and we trim the *middle*, so that we render
1291        // LL | ...= [0, 0, 0, ..., 0, 0];
1292        //    |      ^^^^^^^^^^...^^^^^^^ expected `&[u8]`, found `[{integer}; 1680]`
1293        for (i, (_pos, annotation)) in annotations_position.iter().enumerate() {
1294            // Skip cases where multiple spans overlap each other.
1295            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                // If the terminal is *too* small, we keep at least a tiny bit of the span for
1302                // display.
1303                let pad = max(margin.column_width / 3, 5);
1304                // Code line
1305                buffer.replace(
1306                    line_offset,
1307                    annotation.start_col.file + pad,
1308                    annotation.end_col.file - pad,
1309                    self.margin(),
1310                );
1311                // Underline line
1312                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    /// Adds a left margin to every line but the first, given a padding length and the label being
1378    /// displayed, keeping the provided highlighting.
1379    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        // The extra 5 ` ` is padding that's always needed to align to the `note: `:
1389        //
1390        //   error: message
1391        //     --> file.rs:13:20
1392        //      |
1393        //   13 |     <CODE>
1394        //      |      ^^^^
1395        //      |
1396        //      = note: multiline
1397        //              message
1398        //   ++^^^----xx
1399        //    |  |   | |
1400        //    |  |   | magic `2`
1401        //    |  |   length of label
1402        //    |  magic `3`
1403        //    `max_line_num_len`
1404        let padding = " ".repeat(padding + label.len() + 5);
1405
1406        /// Returns `override` if it is present and `style` is `NoStyle` or `style` otherwise
1407        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        // Provided the following diagnostic message:
1417        //
1418        //     let msgs = vec![
1419        //       ("
1420        //       ("highlighted multiline\nstring to\nsee how it ", Style::NoStyle),
1421        //       ("looks", Style::Highlight),
1422        //       ("with\nvery ", Style::NoStyle),
1423        //       ("weird", Style::Highlight),
1424        //       (" formats\n", Style::NoStyle),
1425        //       ("see?", Style::Highlight),
1426        //     ];
1427        //
1428        // the expected output on a note is (* surround the highlighted text)
1429        //
1430        //        = note: highlighted multiline
1431        //                string to
1432        //                see how it *looks* with
1433        //                very *weird* formats
1434        //                see?
1435        for (text, style) in msgs.iter() {
1436            let text = self.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        emitted_at: Option<&DiagLocation>,
1465        is_cont: bool,
1466    ) -> io::Result<()> {
1467        let mut buffer = StyledBuffer::new();
1468
1469        if !msp.has_primary_spans() && !msp.has_span_labels() && is_secondary && !self.short_message
1470        {
1471            // This is a secondary message with no span info
1472            for _ in 0..max_line_num_len {
1473                buffer.prepend(0, " ", Style::NoStyle);
1474            }
1475            self.draw_note_separator(&mut buffer, 0, max_line_num_len + 1, is_cont);
1476            if *level != Level::FailureNote {
1477                buffer.append(0, level.to_str(), Style::MainHeaderMsg);
1478                buffer.append(0, ": ", Style::NoStyle);
1479            }
1480            let printed_lines =
1481                self.msgs_to_buffer(&mut buffer, msgs, args, max_line_num_len, "note", None);
1482            if is_cont && matches!(self.theme, OutputTheme::Unicode) {
1483                // There's another note after this one, associated to the subwindow above.
1484                // We write additional vertical lines to join them:
1485                //   ╭▸ test.rs:3:3
1486                //   │
1487                // 3 │   code
1488                //   │   ━━━━
1489                //   │
1490                //   ├ note: foo
1491                //   │       bar
1492                //   ╰ note: foo
1493                //           bar
1494                for i in 1..=printed_lines {
1495                    self.draw_col_separator_no_space(&mut buffer, i, max_line_num_len + 1);
1496                }
1497            }
1498        } else {
1499            let mut label_width = 0;
1500            // The failure note level itself does not provide any useful diagnostic information
1501            if *level != Level::FailureNote {
1502                buffer.append(0, level.to_str(), Style::Level(*level));
1503                label_width += level.to_str().len();
1504            }
1505            if let Some(code) = code {
1506                buffer.append(0, "[", Style::Level(*level));
1507                let code = if let TerminalUrl::Yes = self.terminal_url {
1508                    let path = "https://doc.rust-lang.org/error_codes";
1509                    format!("\x1b]8;;{path}/{code}.html\x07{code}\x1b]8;;\x07")
1510                } else {
1511                    code.to_string()
1512                };
1513                buffer.append(0, &code, Style::Level(*level));
1514                buffer.append(0, "]", Style::Level(*level));
1515                label_width += 2 + code.len();
1516            }
1517            let header_style = if is_secondary {
1518                Style::HeaderMsg
1519            } else if self.short_message {
1520                // For short messages avoid bolding the message, as it doesn't look great (#63835).
1521                Style::NoStyle
1522            } else {
1523                Style::MainHeaderMsg
1524            };
1525            if *level != Level::FailureNote {
1526                buffer.append(0, ": ", header_style);
1527                label_width += 2;
1528            }
1529            let mut line = 0;
1530            for (text, style) in msgs.iter() {
1531                let text = self.translate_message(text, args).map_err(Report::new).unwrap();
1532                // Account for newlines to align output to its label.
1533                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                // We add lines above, but if the last line has no explicit newline (which would
1549                // yield an empty line), then we revert one line up to continue with the next
1550                // styled text chunk on the same line as the last one from the prior one. Otherwise
1551                // every `text` would appear on their own line (because even though they didn't end
1552                // in '\n', they advanced `line` by one).
1553                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.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
1579        // Make sure our primary file comes first
1580        let primary_span = msp.primary_span().unwrap_or_default();
1581        let (Some(sm), false) = (self.sm.as_ref(), primary_span.is_dummy()) else {
1582            // If we don't have span information, emit and exit
1583            return emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message);
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        // Print out the annotate source lines that correspond with the error
1593        for annotated_file in annotated_files {
1594            // we can't annotate anything if the source is unavailable.
1595            if !should_show_source_code(
1596                &self.ignored_directories_in_source_blocks,
1597                sm,
1598                &annotated_file.file,
1599            ) {
1600                if !self.short_message {
1601                    // We'll just print an unannotated message.
1602                    for (annotation_id, line) in annotated_file.lines.iter().enumerate() {
1603                        let mut annotations = line.annotations.clone();
1604                        annotations.sort_by_key(|a| Reverse(a.start_col));
1605                        let mut line_idx = buffer.num_lines();
1606
1607                        let labels: Vec<_> = annotations
1608                            .iter()
1609                            .filter_map(|a| Some((a.label.as_ref()?, a.is_primary)))
1610                            .filter(|(l, _)| !l.is_empty())
1611                            .collect();
1612
1613                        if annotation_id == 0 || !labels.is_empty() {
1614                            buffer.append(
1615                                line_idx,
1616                                &format!(
1617                                    "{}:{}:{}",
1618                                    sm.filename_for_diagnostics(&annotated_file.file.name),
1619                                    sm.doctest_offset_line(
1620                                        &annotated_file.file.name,
1621                                        line.line_index
1622                                    ),
1623                                    annotations[0].start_col.file + 1,
1624                                ),
1625                                Style::LineAndColumn,
1626                            );
1627                            if annotation_id == 0 {
1628                                buffer.prepend(line_idx, self.file_start(), Style::LineNumber);
1629                            } else {
1630                                buffer.prepend(
1631                                    line_idx,
1632                                    self.secondary_file_start(),
1633                                    Style::LineNumber,
1634                                );
1635                            }
1636                            for _ in 0..max_line_num_len {
1637                                buffer.prepend(line_idx, " ", Style::NoStyle);
1638                            }
1639                            line_idx += 1;
1640                        }
1641                        for (label, is_primary) in labels.into_iter() {
1642                            let style = if is_primary {
1643                                Style::LabelPrimary
1644                            } else {
1645                                Style::LabelSecondary
1646                            };
1647                            let pipe = self.col_separator();
1648                            buffer.prepend(line_idx, &format!(" {pipe}"), Style::LineNumber);
1649                            for _ in 0..max_line_num_len {
1650                                buffer.prepend(line_idx, " ", Style::NoStyle);
1651                            }
1652                            line_idx += 1;
1653                            let chr = self.note_separator();
1654                            buffer.append(line_idx, &format!(" {chr} note: "), style);
1655                            for _ in 0..max_line_num_len {
1656                                buffer.prepend(line_idx, " ", Style::NoStyle);
1657                            }
1658                            buffer.append(line_idx, label, style);
1659                            line_idx += 1;
1660                        }
1661                    }
1662                }
1663                continue;
1664            }
1665
1666            // print out the span location and spacer before we print the annotated source
1667            // to do this, we need to know if this span will be primary
1668            let is_primary = primary_lo.file.name == annotated_file.file.name;
1669            if is_primary {
1670                let loc = primary_lo.clone();
1671                if !self.short_message {
1672                    // remember where we are in the output buffer for easy reference
1673                    let buffer_msg_line_offset = buffer.num_lines();
1674
1675                    buffer.prepend(buffer_msg_line_offset, self.file_start(), Style::LineNumber);
1676                    buffer.append(
1677                        buffer_msg_line_offset,
1678                        &format!(
1679                            "{}:{}:{}",
1680                            sm.filename_for_diagnostics(&loc.file.name),
1681                            sm.doctest_offset_line(&loc.file.name, loc.line),
1682                            loc.col.0 + 1,
1683                        ),
1684                        Style::LineAndColumn,
1685                    );
1686                    for _ in 0..max_line_num_len {
1687                        buffer.prepend(buffer_msg_line_offset, " ", Style::NoStyle);
1688                    }
1689                } else {
1690                    buffer.prepend(
1691                        0,
1692                        &format!(
1693                            "{}:{}:{}: ",
1694                            sm.filename_for_diagnostics(&loc.file.name),
1695                            sm.doctest_offset_line(&loc.file.name, loc.line),
1696                            loc.col.0 + 1,
1697                        ),
1698                        Style::LineAndColumn,
1699                    );
1700                }
1701            } else if !self.short_message {
1702                // remember where we are in the output buffer for easy reference
1703                let buffer_msg_line_offset = buffer.num_lines();
1704
1705                // Add spacing line, as shown:
1706                //   --> $DIR/file:54:15
1707                //    |
1708                // LL |         code
1709                //    |         ^^^^
1710                //    | (<- It prints *this* line)
1711                //   ::: $DIR/other_file.rs:15:5
1712                //    |
1713                // LL |     code
1714                //    |     ----
1715                self.draw_col_separator_no_space(
1716                    &mut buffer,
1717                    buffer_msg_line_offset,
1718                    max_line_num_len + 1,
1719                );
1720
1721                // Then, the secondary file indicator
1722                buffer.prepend(
1723                    buffer_msg_line_offset + 1,
1724                    self.secondary_file_start(),
1725                    Style::LineNumber,
1726                );
1727                let loc = if let Some(first_line) = annotated_file.lines.first() {
1728                    let col = if let Some(first_annotation) = first_line.annotations.first() {
1729                        format!(":{}", first_annotation.start_col.file + 1)
1730                    } else {
1731                        String::new()
1732                    };
1733                    format!(
1734                        "{}:{}{}",
1735                        sm.filename_for_diagnostics(&annotated_file.file.name),
1736                        sm.doctest_offset_line(&annotated_file.file.name, first_line.line_index),
1737                        col
1738                    )
1739                } else {
1740                    format!("{}", sm.filename_for_diagnostics(&annotated_file.file.name))
1741                };
1742                buffer.append(buffer_msg_line_offset + 1, &loc, Style::LineAndColumn);
1743                for _ in 0..max_line_num_len {
1744                    buffer.prepend(buffer_msg_line_offset + 1, " ", Style::NoStyle);
1745                }
1746            }
1747
1748            if !self.short_message {
1749                // Put in the spacer between the location and annotated source
1750                let buffer_msg_line_offset = buffer.num_lines();
1751                self.draw_col_separator_no_space(
1752                    &mut buffer,
1753                    buffer_msg_line_offset,
1754                    max_line_num_len + 1,
1755                );
1756
1757                // Contains the vertical lines' positions for active multiline annotations
1758                let mut multilines = FxIndexMap::default();
1759
1760                // Get the left-side margin to remove it
1761                let mut whitespace_margin = usize::MAX;
1762                for line_idx in 0..annotated_file.lines.len() {
1763                    let file = Arc::clone(&annotated_file.file);
1764                    let line = &annotated_file.lines[line_idx];
1765                    if let Some(source_string) =
1766                        line.line_index.checked_sub(1).and_then(|l| file.get_line(l))
1767                    {
1768                        // Whitespace can only be removed (aka considered leading)
1769                        // if the lexer considers it whitespace.
1770                        // non-rustc_lexer::is_whitespace() chars are reported as an
1771                        // error (ex. no-break-spaces \u{a0}), and thus can't be considered
1772                        // for removal during error reporting.
1773                        // FIXME: doesn't account for '\t' properly.
1774                        let leading_whitespace = source_string
1775                            .chars()
1776                            .take_while(|c| rustc_lexer::is_whitespace(*c))
1777                            .count();
1778                        if source_string.chars().any(|c| !rustc_lexer::is_whitespace(c)) {
1779                            whitespace_margin = min(whitespace_margin, leading_whitespace);
1780                        }
1781                    }
1782                }
1783                if whitespace_margin == usize::MAX {
1784                    whitespace_margin = 0;
1785                }
1786
1787                // Left-most column any visible span points at.
1788                let mut span_left_margin = usize::MAX;
1789                for line in &annotated_file.lines {
1790                    for ann in &line.annotations {
1791                        span_left_margin = min(span_left_margin, ann.start_col.file);
1792                        span_left_margin = min(span_left_margin, ann.end_col.file);
1793                    }
1794                }
1795                if span_left_margin == usize::MAX {
1796                    span_left_margin = 0;
1797                }
1798
1799                // Right-most column any visible span points at.
1800                let mut span_right_margin = 0;
1801                let mut label_right_margin = 0;
1802                let mut max_line_len = 0;
1803                for line in &annotated_file.lines {
1804                    max_line_len = max(
1805                        max_line_len,
1806                        line.line_index
1807                            .checked_sub(1)
1808                            .and_then(|l| annotated_file.file.get_line(l))
1809                            .map_or(0, |s| s.len()),
1810                    );
1811                    for ann in &line.annotations {
1812                        span_right_margin = max(span_right_margin, ann.start_col.file);
1813                        span_right_margin = max(span_right_margin, ann.end_col.file);
1814                        // FIXME: account for labels not in the same line
1815                        let label_right = ann.label.as_ref().map_or(0, |l| l.len() + 1);
1816                        label_right_margin =
1817                            max(label_right_margin, ann.end_col.file + label_right);
1818                    }
1819                }
1820
1821                let width_offset = 3 + max_line_num_len;
1822                let code_offset = if annotated_file.multiline_depth == 0 {
1823                    width_offset
1824                } else {
1825                    width_offset + annotated_file.multiline_depth + 1
1826                };
1827
1828                let column_width = self.column_width(code_offset);
1829
1830                let margin = Margin::new(
1831                    whitespace_margin,
1832                    span_left_margin,
1833                    span_right_margin,
1834                    label_right_margin,
1835                    column_width,
1836                    max_line_len,
1837                );
1838
1839                // Next, output the annotate source for this file
1840                for line_idx in 0..annotated_file.lines.len() {
1841                    let previous_buffer_line = buffer.num_lines();
1842
1843                    let depths = self.render_source_line(
1844                        &mut buffer,
1845                        Arc::clone(&annotated_file.file),
1846                        &annotated_file.lines[line_idx],
1847                        width_offset,
1848                        code_offset,
1849                        margin,
1850                        !is_cont && line_idx + 1 == annotated_file.lines.len(),
1851                    );
1852
1853                    let mut to_add = FxHashMap::default();
1854
1855                    for (depth, style) in depths {
1856                        // FIXME(#120456) - is `swap_remove` correct?
1857                        if multilines.swap_remove(&depth).is_none() {
1858                            to_add.insert(depth, style);
1859                        }
1860                    }
1861
1862                    // Set the multiline annotation vertical lines to the left of
1863                    // the code in this line.
1864                    for (depth, style) in &multilines {
1865                        for line in previous_buffer_line..buffer.num_lines() {
1866                            self.draw_multiline_line(
1867                                &mut buffer,
1868                                line,
1869                                width_offset,
1870                                *depth,
1871                                *style,
1872                            );
1873                        }
1874                    }
1875                    // check to see if we need to print out or elide lines that come between
1876                    // this annotated line and the next one.
1877                    if line_idx < (annotated_file.lines.len() - 1) {
1878                        let line_idx_delta = annotated_file.lines[line_idx + 1].line_index
1879                            - annotated_file.lines[line_idx].line_index;
1880                        if line_idx_delta > 2 {
1881                            let last_buffer_line_num = buffer.num_lines();
1882                            self.draw_line_separator(
1883                                &mut buffer,
1884                                last_buffer_line_num,
1885                                width_offset,
1886                            );
1887
1888                            // Set the multiline annotation vertical lines on `...` bridging line.
1889                            for (depth, style) in &multilines {
1890                                self.draw_multiline_line(
1891                                    &mut buffer,
1892                                    last_buffer_line_num,
1893                                    width_offset,
1894                                    *depth,
1895                                    *style,
1896                                );
1897                            }
1898                            if let Some(line) = annotated_file.lines.get(line_idx) {
1899                                for ann in &line.annotations {
1900                                    if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1901                                    {
1902                                        // In the case where we have elided the entire start of the
1903                                        // multispan because those lines were empty, we still need
1904                                        // to draw the `|`s across the `...`.
1905                                        self.draw_multiline_line(
1906                                            &mut buffer,
1907                                            last_buffer_line_num,
1908                                            width_offset,
1909                                            pos,
1910                                            if ann.is_primary {
1911                                                Style::UnderlinePrimary
1912                                            } else {
1913                                                Style::UnderlineSecondary
1914                                            },
1915                                        );
1916                                    }
1917                                }
1918                            }
1919                        } else if line_idx_delta == 2 {
1920                            let unannotated_line = annotated_file
1921                                .file
1922                                .get_line(annotated_file.lines[line_idx].line_index)
1923                                .unwrap_or_else(|| Cow::from(""));
1924
1925                            let last_buffer_line_num = buffer.num_lines();
1926
1927                            self.draw_line(
1928                                &mut buffer,
1929                                &normalize_whitespace(&unannotated_line),
1930                                annotated_file.lines[line_idx + 1].line_index - 1,
1931                                last_buffer_line_num,
1932                                width_offset,
1933                                code_offset,
1934                                margin,
1935                            );
1936
1937                            for (depth, style) in &multilines {
1938                                self.draw_multiline_line(
1939                                    &mut buffer,
1940                                    last_buffer_line_num,
1941                                    width_offset,
1942                                    *depth,
1943                                    *style,
1944                                );
1945                            }
1946                            if let Some(line) = annotated_file.lines.get(line_idx) {
1947                                for ann in &line.annotations {
1948                                    if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1949                                    {
1950                                        self.draw_multiline_line(
1951                                            &mut buffer,
1952                                            last_buffer_line_num,
1953                                            width_offset,
1954                                            pos,
1955                                            if ann.is_primary {
1956                                                Style::UnderlinePrimary
1957                                            } else {
1958                                                Style::UnderlineSecondary
1959                                            },
1960                                        );
1961                                    }
1962                                }
1963                            }
1964                        }
1965                    }
1966
1967                    multilines.extend(&to_add);
1968                }
1969            }
1970            trace!("buffer: {:#?}", buffer.render());
1971        }
1972
1973        if let Some(tracked) = emitted_at {
1974            let track = format!("-Ztrack-diagnostics: created at {tracked}");
1975            let len = buffer.num_lines();
1976            buffer.append(len, &track, Style::NoStyle);
1977        }
1978
1979        // final step: take our styled buffer, render it, then output it
1980        emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
1981
1982        Ok(())
1983    }
1984
1985    fn column_width(&self, code_offset: usize) -> usize {
1986        if let Some(width) = self.diagnostic_width {
1987            width.saturating_sub(code_offset)
1988        } else if self.ui_testing || cfg!(miri) {
1989            DEFAULT_COLUMN_WIDTH
1990        } else {
1991            termize::dimensions()
1992                .map(|(w, _)| w.saturating_sub(code_offset))
1993                .unwrap_or(DEFAULT_COLUMN_WIDTH)
1994        }
1995    }
1996
1997    fn emit_suggestion_default(
1998        &mut self,
1999        span: &MultiSpan,
2000        suggestion: &CodeSuggestion,
2001        args: &FluentArgs<'_>,
2002        level: &Level,
2003        max_line_num_len: usize,
2004    ) -> io::Result<()> {
2005        let Some(ref sm) = self.sm else {
2006            return Ok(());
2007        };
2008
2009        // Render the replacements for each suggestion
2010        let suggestions = suggestion.splice_lines(sm);
2011        debug!(?suggestions);
2012
2013        if suggestions.is_empty() {
2014            // Here we check if there are suggestions that have actual code changes. We sometimes
2015            // suggest the same code that is already there, instead of changing how we produce the
2016            // suggestions and filtering there, we just don't emit the suggestion.
2017            // Suggestions coming from macros can also have malformed spans. This is a heavy handed
2018            // approach to avoid ICEs by ignoring the suggestion outright.
2019            return Ok(());
2020        }
2021
2022        let mut buffer = StyledBuffer::new();
2023
2024        // Render the suggestion message
2025        buffer.append(0, level.to_str(), Style::Level(*level));
2026        buffer.append(0, ": ", Style::HeaderMsg);
2027
2028        let mut msg = vec![(suggestion.msg.to_owned(), Style::NoStyle)];
2029        if suggestions
2030            .iter()
2031            .take(MAX_SUGGESTIONS)
2032            .any(|(_, _, _, only_capitalization)| *only_capitalization)
2033        {
2034            msg.push((" (notice the capitalization difference)".into(), Style::NoStyle));
2035        }
2036        self.msgs_to_buffer(
2037            &mut buffer,
2038            &msg,
2039            args,
2040            max_line_num_len,
2041            "suggestion",
2042            Some(Style::HeaderMsg),
2043        );
2044
2045        let other_suggestions = suggestions.len().saturating_sub(MAX_SUGGESTIONS);
2046
2047        let mut row_num = 2;
2048        for (i, (complete, parts, highlights, _)) in
2049            suggestions.into_iter().enumerate().take(MAX_SUGGESTIONS)
2050        {
2051            debug!(?complete, ?parts, ?highlights);
2052
2053            let has_deletion =
2054                parts.iter().any(|p| p.is_deletion(sm) || p.is_destructive_replacement(sm));
2055            let is_multiline = complete.lines().count() > 1;
2056
2057            if i == 0 {
2058                self.draw_col_separator_start(&mut buffer, row_num - 1, max_line_num_len + 1);
2059            } else {
2060                buffer.puts(
2061                    row_num - 1,
2062                    max_line_num_len + 1,
2063                    self.multi_suggestion_separator(),
2064                    Style::LineNumber,
2065                );
2066            }
2067            if let Some(span) = span.primary_span() {
2068                // Compare the primary span of the diagnostic with the span of the suggestion
2069                // being emitted. If they belong to the same file, we don't *need* to show the
2070                // file name, saving in verbosity, but if it *isn't* we do need it, otherwise we're
2071                // telling users to make a change but not clarifying *where*.
2072                let loc = sm.lookup_char_pos(parts[0].span.lo());
2073                if loc.file.name != sm.span_to_filename(span) && loc.file.name.is_real() {
2074                    // --> file.rs:line:col
2075                    //  |
2076                    let arrow = self.file_start();
2077                    buffer.puts(row_num - 1, 0, arrow, Style::LineNumber);
2078                    let filename = sm.filename_for_diagnostics(&loc.file.name);
2079                    let offset = sm.doctest_offset_line(&loc.file.name, loc.line);
2080                    let message = format!("{}:{}:{}", filename, offset, loc.col.0 + 1);
2081                    if row_num == 2 {
2082                        let col = usize::max(max_line_num_len + 1, arrow.len());
2083                        buffer.puts(1, col, &message, Style::LineAndColumn);
2084                    } else {
2085                        buffer.append(row_num - 1, &message, Style::LineAndColumn);
2086                    }
2087                    for _ in 0..max_line_num_len {
2088                        buffer.prepend(row_num - 1, " ", Style::NoStyle);
2089                    }
2090                    self.draw_col_separator_no_space(&mut buffer, row_num, max_line_num_len + 1);
2091                    row_num += 1;
2092                }
2093            }
2094            let show_code_change = if has_deletion && !is_multiline {
2095                DisplaySuggestion::Diff
2096            } else if let [part] = &parts[..]
2097                && part.snippet.ends_with('\n')
2098                && part.snippet.trim() == complete.trim()
2099            {
2100                // We are adding a line(s) of code before code that was already there.
2101                DisplaySuggestion::Add
2102            } else if (parts.len() != 1 || parts[0].snippet.trim() != complete.trim())
2103                && !is_multiline
2104            {
2105                DisplaySuggestion::Underline
2106            } else {
2107                DisplaySuggestion::None
2108            };
2109
2110            if let DisplaySuggestion::Diff = show_code_change {
2111                row_num += 1;
2112            }
2113
2114            let file_lines = sm
2115                .span_to_lines(parts[0].span)
2116                .expect("span_to_lines failed when emitting suggestion");
2117
2118            assert!(!file_lines.lines.is_empty() || parts[0].span.is_dummy());
2119
2120            let line_start = sm.lookup_char_pos(parts[0].span.lo()).line;
2121            let mut lines = complete.lines();
2122            if lines.clone().next().is_none() {
2123                // Account for a suggestion to completely remove a line(s) with whitespace (#94192).
2124                let line_end = sm.lookup_char_pos(parts[0].span.hi()).line;
2125                for line in line_start..=line_end {
2126                    buffer.puts(
2127                        row_num - 1 + line - line_start,
2128                        0,
2129                        &self.maybe_anonymized(line),
2130                        Style::LineNumber,
2131                    );
2132                    buffer.puts(
2133                        row_num - 1 + line - line_start,
2134                        max_line_num_len + 1,
2135                        "- ",
2136                        Style::Removal,
2137                    );
2138                    buffer.puts(
2139                        row_num - 1 + line - line_start,
2140                        max_line_num_len + 3,
2141                        &normalize_whitespace(&file_lines.file.get_line(line - 1).unwrap()),
2142                        Style::Removal,
2143                    );
2144                }
2145                row_num += line_end - line_start;
2146            }
2147            let mut unhighlighted_lines = Vec::new();
2148            let mut last_pos = 0;
2149            let mut is_item_attribute = false;
2150            for (line_pos, (line, highlight_parts)) in lines.by_ref().zip(highlights).enumerate() {
2151                last_pos = line_pos;
2152                debug!(%line_pos, %line, ?highlight_parts);
2153
2154                // Remember lines that are not highlighted to hide them if needed
2155                if highlight_parts.is_empty() {
2156                    unhighlighted_lines.push((line_pos, line));
2157                    continue;
2158                }
2159                if highlight_parts.len() == 1
2160                    && line.trim().starts_with("#[")
2161                    && line.trim().ends_with(']')
2162                {
2163                    is_item_attribute = true;
2164                }
2165
2166                match unhighlighted_lines.len() {
2167                    0 => (),
2168                    // Since we show first line, "..." line and last line,
2169                    // There is no reason to hide if there are 3 or less lines
2170                    // (because then we just replace a line with ... which is
2171                    // not helpful)
2172                    n if n <= 3 => unhighlighted_lines.drain(..).for_each(|(p, l)| {
2173                        self.draw_code_line(
2174                            &mut buffer,
2175                            &mut row_num,
2176                            &[],
2177                            p + line_start,
2178                            l,
2179                            show_code_change,
2180                            max_line_num_len,
2181                            &file_lines,
2182                            is_multiline,
2183                        )
2184                    }),
2185                    // Print first unhighlighted line, "..." and last unhighlighted line, like so:
2186                    //
2187                    // LL | this line was highlighted
2188                    // LL | this line is just for context
2189                    // ...
2190                    // LL | this line is just for context
2191                    // LL | this line was highlighted
2192                    _ => {
2193                        let last_line = unhighlighted_lines.pop();
2194                        let first_line = unhighlighted_lines.drain(..).next();
2195
2196                        if let Some((p, l)) = first_line {
2197                            self.draw_code_line(
2198                                &mut buffer,
2199                                &mut row_num,
2200                                &[],
2201                                p + line_start,
2202                                l,
2203                                show_code_change,
2204                                max_line_num_len,
2205                                &file_lines,
2206                                is_multiline,
2207                            )
2208                        }
2209
2210                        let placeholder = self.margin();
2211                        let padding = str_width(placeholder);
2212                        buffer.puts(
2213                            row_num,
2214                            max_line_num_len.saturating_sub(padding),
2215                            placeholder,
2216                            Style::LineNumber,
2217                        );
2218                        row_num += 1;
2219
2220                        if let Some((p, l)) = last_line {
2221                            self.draw_code_line(
2222                                &mut buffer,
2223                                &mut row_num,
2224                                &[],
2225                                p + line_start,
2226                                l,
2227                                show_code_change,
2228                                max_line_num_len,
2229                                &file_lines,
2230                                is_multiline,
2231                            )
2232                        }
2233                    }
2234                }
2235
2236                self.draw_code_line(
2237                    &mut buffer,
2238                    &mut row_num,
2239                    &highlight_parts,
2240                    line_pos + line_start,
2241                    line,
2242                    show_code_change,
2243                    max_line_num_len,
2244                    &file_lines,
2245                    is_multiline,
2246                )
2247            }
2248            if let DisplaySuggestion::Add = show_code_change
2249                && is_item_attribute
2250            {
2251                // The suggestion adds an entire line of code, ending on a newline, so we'll also
2252                // print the *following* line, to provide context of what we're advising people to
2253                // do. Otherwise you would only see contextless code that can be confused for
2254                // already existing code, despite the colors and UI elements.
2255                // We special case `#[derive(_)]\n` and other attribute suggestions, because those
2256                // are the ones where context is most useful.
2257                let file_lines = sm
2258                    .span_to_lines(parts[0].span.shrink_to_hi())
2259                    .expect("span_to_lines failed when emitting suggestion");
2260                let line_num = sm.lookup_char_pos(parts[0].span.lo()).line;
2261                if let Some(line) = file_lines.file.get_line(line_num - 1) {
2262                    let line = normalize_whitespace(&line);
2263                    self.draw_code_line(
2264                        &mut buffer,
2265                        &mut row_num,
2266                        &[],
2267                        line_num + last_pos + 1,
2268                        &line,
2269                        DisplaySuggestion::None,
2270                        max_line_num_len,
2271                        &file_lines,
2272                        is_multiline,
2273                    )
2274                }
2275            }
2276
2277            // This offset and the ones below need to be signed to account for replacement code
2278            // that is shorter than the original code.
2279            let mut offsets: Vec<(usize, isize)> = Vec::new();
2280            // Only show an underline in the suggestions if the suggestion is not the
2281            // entirety of the code being shown and the displayed code is not multiline.
2282            if let DisplaySuggestion::Diff | DisplaySuggestion::Underline | DisplaySuggestion::Add =
2283                show_code_change
2284            {
2285                for part in parts {
2286                    let snippet = if let Ok(snippet) = sm.span_to_snippet(part.span) {
2287                        snippet
2288                    } else {
2289                        String::new()
2290                    };
2291                    let span_start_pos = sm.lookup_char_pos(part.span.lo()).col_display;
2292                    let span_end_pos = sm.lookup_char_pos(part.span.hi()).col_display;
2293
2294                    // If this addition is _only_ whitespace, then don't trim it,
2295                    // or else we're just not rendering anything.
2296                    let is_whitespace_addition = part.snippet.trim().is_empty();
2297
2298                    // Do not underline the leading...
2299                    let start = if is_whitespace_addition {
2300                        0
2301                    } else {
2302                        part.snippet.len().saturating_sub(part.snippet.trim_start().len())
2303                    };
2304                    // ...or trailing spaces. Account for substitutions containing unicode
2305                    // characters.
2306                    let sub_len: usize = str_width(if is_whitespace_addition {
2307                        &part.snippet
2308                    } else {
2309                        part.snippet.trim()
2310                    });
2311
2312                    let offset: isize = offsets
2313                        .iter()
2314                        .filter_map(
2315                            |(start, v)| if span_start_pos < *start { None } else { Some(v) },
2316                        )
2317                        .sum();
2318                    let underline_start = (span_start_pos + start) as isize + offset;
2319                    let underline_end = (span_start_pos + start + sub_len) as isize + offset;
2320                    assert!(underline_start >= 0 && underline_end >= 0);
2321                    let padding: usize = max_line_num_len + 3;
2322                    for p in underline_start..underline_end {
2323                        if let DisplaySuggestion::Underline = show_code_change
2324                            && is_different(sm, &part.snippet, part.span)
2325                        {
2326                            // If this is a replacement, underline with `~`, if this is an addition
2327                            // underline with `+`.
2328                            buffer.putc(
2329                                row_num,
2330                                (padding as isize + p) as usize,
2331                                if part.is_addition(sm) { '+' } else { self.diff() },
2332                                Style::Addition,
2333                            );
2334                        }
2335                    }
2336                    if let DisplaySuggestion::Diff = show_code_change {
2337                        // Colorize removal with red in diff format.
2338
2339                        // Below, there's some tricky buffer indexing going on. `row_num` at this
2340                        // point corresponds to:
2341                        //
2342                        //    |
2343                        // LL | CODE
2344                        //    | ++++  <- `row_num`
2345                        //
2346                        // in the buffer. When we have a diff format output, we end up with
2347                        //
2348                        //    |
2349                        // LL - OLDER   <- row_num - 2
2350                        // LL + NEWER
2351                        //    |         <- row_num
2352                        //
2353                        // The `row_num - 2` is to select the buffer line that has the "old version
2354                        // of the diff" at that point. When the removal is a single line, `i` is
2355                        // `0`, `newlines` is `1` so `(newlines - i - 1)` ends up being `0`, so row
2356                        // points at `LL - OLDER`. When the removal corresponds to multiple lines,
2357                        // we end up with `newlines > 1` and `i` being `0..newlines - 1`.
2358                        //
2359                        //    |
2360                        // LL - OLDER   <- row_num - 2 - (newlines - last_i - 1)
2361                        // LL - CODE
2362                        // LL - BEING
2363                        // LL - REMOVED <- row_num - 2 - (newlines - first_i - 1)
2364                        // LL + NEWER
2365                        //    |         <- row_num
2366
2367                        let newlines = snippet.lines().count();
2368                        if newlines > 0 && row_num > newlines {
2369                            // Account for removals where the part being removed spans multiple
2370                            // lines.
2371                            // FIXME: We check the number of rows because in some cases, like in
2372                            // `tests/ui/lint/invalid-nan-comparison-suggestion.rs`, the rendered
2373                            // suggestion will only show the first line of code being replaced. The
2374                            // proper way of doing this would be to change the suggestion rendering
2375                            // logic to show the whole prior snippet, but the current output is not
2376                            // too bad to begin with, so we side-step that issue here.
2377                            for (i, line) in snippet.lines().enumerate() {
2378                                let line = normalize_whitespace(line);
2379                                let row = row_num - 2 - (newlines - i - 1);
2380                                // On the first line, we highlight between the start of the part
2381                                // span, and the end of that line.
2382                                // On the last line, we highlight between the start of the line, and
2383                                // the column of the part span end.
2384                                // On all others, we highlight the whole line.
2385                                let start = if i == 0 {
2386                                    (padding as isize + span_start_pos as isize) as usize
2387                                } else {
2388                                    padding
2389                                };
2390                                let end = if i == 0 {
2391                                    (padding as isize
2392                                        + span_start_pos as isize
2393                                        + line.len() as isize)
2394                                        as usize
2395                                } else if i == newlines - 1 {
2396                                    (padding as isize + span_end_pos as isize) as usize
2397                                } else {
2398                                    (padding as isize + line.len() as isize) as usize
2399                                };
2400                                buffer.set_style_range(row, start, end, Style::Removal, true);
2401                            }
2402                        } else {
2403                            // The removed code fits all in one line.
2404                            buffer.set_style_range(
2405                                row_num - 2,
2406                                (padding as isize + span_start_pos as isize) as usize,
2407                                (padding as isize + span_end_pos as isize) as usize,
2408                                Style::Removal,
2409                                true,
2410                            );
2411                        }
2412                    }
2413
2414                    // length of the code after substitution
2415                    let full_sub_len = str_width(&part.snippet) as isize;
2416
2417                    // length of the code to be substituted
2418                    let snippet_len = span_end_pos as isize - span_start_pos as isize;
2419                    // For multiple substitutions, use the position *after* the previous
2420                    // substitutions have happened, only when further substitutions are
2421                    // located strictly after.
2422                    offsets.push((span_end_pos, full_sub_len - snippet_len));
2423                }
2424                row_num += 1;
2425            }
2426
2427            // if we elided some lines, add an ellipsis
2428            if lines.next().is_some() {
2429                let placeholder = self.margin();
2430                let padding = str_width(placeholder);
2431                buffer.puts(
2432                    row_num,
2433                    max_line_num_len.saturating_sub(padding),
2434                    placeholder,
2435                    Style::LineNumber,
2436                );
2437            } else {
2438                let row = match show_code_change {
2439                    DisplaySuggestion::Diff
2440                    | DisplaySuggestion::Add
2441                    | DisplaySuggestion::Underline => row_num - 1,
2442                    DisplaySuggestion::None => row_num,
2443                };
2444                self.draw_col_separator_end(&mut buffer, row, max_line_num_len + 1);
2445                row_num = row + 1;
2446            }
2447        }
2448        if other_suggestions > 0 {
2449            let msg = format!(
2450                "and {} other candidate{}",
2451                other_suggestions,
2452                pluralize!(other_suggestions)
2453            );
2454            buffer.puts(row_num, max_line_num_len + 3, &msg, Style::NoStyle);
2455        }
2456
2457        emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
2458        Ok(())
2459    }
2460
2461    #[instrument(level = "trace", skip(self, args, code, children, suggestions))]
2462    fn emit_messages_default(
2463        &mut self,
2464        level: &Level,
2465        messages: &[(DiagMessage, Style)],
2466        args: &FluentArgs<'_>,
2467        code: &Option<ErrCode>,
2468        span: &MultiSpan,
2469        children: &[Subdiag],
2470        suggestions: &[CodeSuggestion],
2471        emitted_at: Option<&DiagLocation>,
2472    ) {
2473        let max_line_num_len = if self.ui_testing {
2474            ANONYMIZED_LINE_NUM.len()
2475        } else {
2476            let n = self.get_max_line_num(span, children);
2477            num_decimal_digits(n)
2478        };
2479
2480        match self.emit_messages_default_inner(
2481            span,
2482            messages,
2483            args,
2484            code,
2485            level,
2486            max_line_num_len,
2487            false,
2488            emitted_at,
2489            !children.is_empty()
2490                || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden),
2491        ) {
2492            Ok(()) => {
2493                if !children.is_empty()
2494                    || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden)
2495                {
2496                    let mut buffer = StyledBuffer::new();
2497                    if !self.short_message {
2498                        if let Some(child) = children.iter().next()
2499                            && child.span.primary_spans().is_empty()
2500                        {
2501                            // We'll continue the vertical bar to point into the next note.
2502                            self.draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1);
2503                        } else {
2504                            // We'll close the vertical bar to visually end the code window.
2505                            self.draw_col_separator_end(&mut buffer, 0, max_line_num_len + 1);
2506                        }
2507                    }
2508                    if let Err(e) = emit_to_destination(
2509                        &buffer.render(),
2510                        level,
2511                        &mut self.dst,
2512                        self.short_message,
2513                    ) {
2514                        panic!("failed to emit error: {e}")
2515                    }
2516                }
2517                if !self.short_message {
2518                    for (i, child) in children.iter().enumerate() {
2519                        assert!(child.level.can_be_subdiag());
2520                        let span = &child.span;
2521                        // FIXME: audit that this behaves correctly with suggestions.
2522                        let should_close = match children.get(i + 1) {
2523                            Some(c) => !c.span.primary_spans().is_empty(),
2524                            None => i + 1 == children.len(),
2525                        };
2526                        if let Err(err) = self.emit_messages_default_inner(
2527                            span,
2528                            &child.messages,
2529                            args,
2530                            &None,
2531                            &child.level,
2532                            max_line_num_len,
2533                            true,
2534                            None,
2535                            !should_close,
2536                        ) {
2537                            panic!("failed to emit error: {err}");
2538                        }
2539                    }
2540                    for (i, sugg) in suggestions.iter().enumerate() {
2541                        match sugg.style {
2542                            SuggestionStyle::CompletelyHidden => {
2543                                // do not display this suggestion, it is meant only for tools
2544                            }
2545                            SuggestionStyle::HideCodeAlways => {
2546                                if let Err(e) = self.emit_messages_default_inner(
2547                                    &MultiSpan::new(),
2548                                    &[(sugg.msg.to_owned(), Style::HeaderMsg)],
2549                                    args,
2550                                    &None,
2551                                    &Level::Help,
2552                                    max_line_num_len,
2553                                    true,
2554                                    None,
2555                                    // FIXME: this needs to account for the suggestion type,
2556                                    //        some don't take any space.
2557                                    i + 1 != suggestions.len(),
2558                                ) {
2559                                    panic!("failed to emit error: {e}");
2560                                }
2561                            }
2562                            SuggestionStyle::HideCodeInline
2563                            | SuggestionStyle::ShowCode
2564                            | SuggestionStyle::ShowAlways => {
2565                                if let Err(e) = self.emit_suggestion_default(
2566                                    span,
2567                                    sugg,
2568                                    args,
2569                                    &Level::Help,
2570                                    max_line_num_len,
2571                                ) {
2572                                    panic!("failed to emit error: {e}");
2573                                }
2574                            }
2575                        }
2576                    }
2577                }
2578            }
2579            Err(e) => panic!("failed to emit error: {e}"),
2580        }
2581
2582        match writeln!(self.dst) {
2583            Err(e) => panic!("failed to emit error: {e}"),
2584            _ => {
2585                if let Err(e) = self.dst.flush() {
2586                    panic!("failed to emit error: {e}")
2587                }
2588            }
2589        }
2590    }
2591
2592    fn draw_code_line(
2593        &self,
2594        buffer: &mut StyledBuffer,
2595        row_num: &mut usize,
2596        highlight_parts: &[SubstitutionHighlight],
2597        line_num: usize,
2598        line_to_add: &str,
2599        show_code_change: DisplaySuggestion,
2600        max_line_num_len: usize,
2601        file_lines: &FileLines,
2602        is_multiline: bool,
2603    ) {
2604        if let DisplaySuggestion::Diff = show_code_change {
2605            // We need to print more than one line if the span we need to remove is multiline.
2606            // For more info: https://github.com/rust-lang/rust/issues/92741
2607            let lines_to_remove = file_lines.lines.iter().take(file_lines.lines.len() - 1);
2608            for (index, line_to_remove) in lines_to_remove.enumerate() {
2609                buffer.puts(
2610                    *row_num - 1,
2611                    0,
2612                    &self.maybe_anonymized(line_num + index),
2613                    Style::LineNumber,
2614                );
2615                buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2616                let line = normalize_whitespace(
2617                    &file_lines.file.get_line(line_to_remove.line_index).unwrap(),
2618                );
2619                buffer.puts(*row_num - 1, max_line_num_len + 3, &line, Style::NoStyle);
2620                *row_num += 1;
2621            }
2622            // If the last line is exactly equal to the line we need to add, we can skip both of
2623            // them. This allows us to avoid output like the following:
2624            // 2 - &
2625            // 2 + if true { true } else { false }
2626            // 3 - if true { true } else { false }
2627            // If those lines aren't equal, we print their diff
2628            let last_line_index = file_lines.lines[file_lines.lines.len() - 1].line_index;
2629            let last_line = &file_lines.file.get_line(last_line_index).unwrap();
2630            if last_line != line_to_add {
2631                buffer.puts(
2632                    *row_num - 1,
2633                    0,
2634                    &self.maybe_anonymized(line_num + file_lines.lines.len() - 1),
2635                    Style::LineNumber,
2636                );
2637                buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2638                buffer.puts(
2639                    *row_num - 1,
2640                    max_line_num_len + 3,
2641                    &normalize_whitespace(last_line),
2642                    Style::NoStyle,
2643                );
2644                if !line_to_add.trim().is_empty() {
2645                    // Check if after the removal, the line is left with only whitespace. If so, we
2646                    // will not show an "addition" line, as removing the whole line is what the user
2647                    // would really want.
2648                    // For example, for the following:
2649                    //   |
2650                    // 2 -     .await
2651                    // 2 +     (note the left over whitespace)
2652                    //   |
2653                    // We really want
2654                    //   |
2655                    // 2 -     .await
2656                    //   |
2657                    // *row_num -= 1;
2658                    buffer.puts(*row_num, 0, &self.maybe_anonymized(line_num), Style::LineNumber);
2659                    buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2660                    buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2661                } else {
2662                    *row_num -= 1;
2663                }
2664            } else {
2665                *row_num -= 2;
2666            }
2667        } else if is_multiline {
2668            buffer.puts(*row_num, 0, &self.maybe_anonymized(line_num), Style::LineNumber);
2669            match &highlight_parts {
2670                [SubstitutionHighlight { start: 0, end }] if *end == line_to_add.len() => {
2671                    buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2672                }
2673                [] => {
2674                    // FIXME: needed? Doesn't get exercised in any test.
2675                    self.draw_col_separator_no_space(buffer, *row_num, max_line_num_len + 1);
2676                }
2677                _ => {
2678                    let diff = self.diff();
2679                    buffer.puts(
2680                        *row_num,
2681                        max_line_num_len + 1,
2682                        &format!("{diff} "),
2683                        Style::Addition,
2684                    );
2685                }
2686            }
2687            //   LL | line_to_add
2688            //   ++^^^
2689            //    |  |
2690            //    |  magic `3`
2691            //    `max_line_num_len`
2692            buffer.puts(
2693                *row_num,
2694                max_line_num_len + 3,
2695                &normalize_whitespace(line_to_add),
2696                Style::NoStyle,
2697            );
2698        } else if let DisplaySuggestion::Add = show_code_change {
2699            buffer.puts(*row_num, 0, &self.maybe_anonymized(line_num), Style::LineNumber);
2700            buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2701            buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2702        } else {
2703            buffer.puts(*row_num, 0, &self.maybe_anonymized(line_num), Style::LineNumber);
2704            self.draw_col_separator(buffer, *row_num, max_line_num_len + 1);
2705            buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2706        }
2707
2708        // Colorize addition/replacements with green.
2709        for &SubstitutionHighlight { start, end } in highlight_parts {
2710            // This is a no-op for empty ranges
2711            if start != end {
2712                // Account for tabs when highlighting (#87972).
2713                let tabs: usize = line_to_add
2714                    .chars()
2715                    .take(start)
2716                    .map(|ch| match ch {
2717                        '\t' => 3,
2718                        _ => 0,
2719                    })
2720                    .sum();
2721                buffer.set_style_range(
2722                    *row_num,
2723                    max_line_num_len + 3 + start + tabs,
2724                    max_line_num_len + 3 + end + tabs,
2725                    Style::Addition,
2726                    true,
2727                );
2728            }
2729        }
2730        *row_num += 1;
2731    }
2732
2733    fn underline(&self, is_primary: bool) -> UnderlineParts {
2734        //               X0 Y0
2735        // label_start > ┯━━━━ < underline
2736        //               │ < vertical_text_line
2737        //               text
2738
2739        //    multiline_start_down ⤷ X0 Y0
2740        //            top_left > ┌───╿──┘ < top_right_flat
2741        //           top_left > ┏│━━━┙ < top_right
2742        // multiline_vertical > ┃│
2743        //                      ┃│   X1 Y1
2744        //                      ┃│   X2 Y2
2745        //                      ┃└────╿──┘ < multiline_end_same_line
2746        //        bottom_left > ┗━━━━━┥ < bottom_right_with_text
2747        //   multiline_horizontal ^   `X` is a good letter
2748
2749        // multiline_whole_line > ┏ X0 Y0
2750        //                        ┃   X1 Y1
2751        //                        ┗━━━━┛ < multiline_end_same_line
2752
2753        // multiline_whole_line > ┏ X0 Y0
2754        //                        ┃ X1 Y1
2755        //                        ┃  ╿ < multiline_end_up
2756        //                        ┗━━┛ < bottom_right
2757
2758        match (self.theme, is_primary) {
2759            (OutputTheme::Ascii, true) => UnderlineParts {
2760                style: Style::UnderlinePrimary,
2761                underline: '^',
2762                label_start: '^',
2763                vertical_text_line: '|',
2764                multiline_vertical: '|',
2765                multiline_horizontal: '_',
2766                multiline_whole_line: '/',
2767                multiline_start_down: '^',
2768                bottom_right: '|',
2769                top_left: ' ',
2770                top_right_flat: '^',
2771                bottom_left: '|',
2772                multiline_end_up: '^',
2773                multiline_end_same_line: '^',
2774                multiline_bottom_right_with_text: '|',
2775            },
2776            (OutputTheme::Ascii, false) => UnderlineParts {
2777                style: Style::UnderlineSecondary,
2778                underline: '-',
2779                label_start: '-',
2780                vertical_text_line: '|',
2781                multiline_vertical: '|',
2782                multiline_horizontal: '_',
2783                multiline_whole_line: '/',
2784                multiline_start_down: '-',
2785                bottom_right: '|',
2786                top_left: ' ',
2787                top_right_flat: '-',
2788                bottom_left: '|',
2789                multiline_end_up: '-',
2790                multiline_end_same_line: '-',
2791                multiline_bottom_right_with_text: '|',
2792            },
2793            (OutputTheme::Unicode, true) => UnderlineParts {
2794                style: Style::UnderlinePrimary,
2795                underline: '━',
2796                label_start: '┯',
2797                vertical_text_line: '│',
2798                multiline_vertical: '┃',
2799                multiline_horizontal: '━',
2800                multiline_whole_line: '┏',
2801                multiline_start_down: '╿',
2802                bottom_right: '┙',
2803                top_left: '┏',
2804                top_right_flat: '┛',
2805                bottom_left: '┗',
2806                multiline_end_up: '╿',
2807                multiline_end_same_line: '┛',
2808                multiline_bottom_right_with_text: '┥',
2809            },
2810            (OutputTheme::Unicode, false) => UnderlineParts {
2811                style: Style::UnderlineSecondary,
2812                underline: '─',
2813                label_start: '┬',
2814                vertical_text_line: '│',
2815                multiline_vertical: '│',
2816                multiline_horizontal: '─',
2817                multiline_whole_line: '┌',
2818                multiline_start_down: '│',
2819                bottom_right: '┘',
2820                top_left: '┌',
2821                top_right_flat: '┘',
2822                bottom_left: '└',
2823                multiline_end_up: '│',
2824                multiline_end_same_line: '┘',
2825                multiline_bottom_right_with_text: '┤',
2826            },
2827        }
2828    }
2829
2830    fn col_separator(&self) -> char {
2831        match self.theme {
2832            OutputTheme::Ascii => '|',
2833            OutputTheme::Unicode => '│',
2834        }
2835    }
2836
2837    fn note_separator(&self) -> char {
2838        match self.theme {
2839            OutputTheme::Ascii => '=',
2840            OutputTheme::Unicode => '╰',
2841        }
2842    }
2843
2844    fn multi_suggestion_separator(&self) -> &'static str {
2845        match self.theme {
2846            OutputTheme::Ascii => "|",
2847            OutputTheme::Unicode => "├╴",
2848        }
2849    }
2850
2851    fn draw_col_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2852        let chr = self.col_separator();
2853        buffer.puts(line, col, &format!("{chr} "), Style::LineNumber);
2854    }
2855
2856    fn draw_col_separator_no_space(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2857        let chr = self.col_separator();
2858        self.draw_col_separator_no_space_with_style(buffer, chr, line, col, Style::LineNumber);
2859    }
2860
2861    fn draw_col_separator_start(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2862        match self.theme {
2863            OutputTheme::Ascii => {
2864                self.draw_col_separator_no_space_with_style(
2865                    buffer,
2866                    '|',
2867                    line,
2868                    col,
2869                    Style::LineNumber,
2870                );
2871            }
2872            OutputTheme::Unicode => {
2873                self.draw_col_separator_no_space_with_style(
2874                    buffer,
2875                    '╭',
2876                    line,
2877                    col,
2878                    Style::LineNumber,
2879                );
2880                self.draw_col_separator_no_space_with_style(
2881                    buffer,
2882                    '╴',
2883                    line,
2884                    col + 1,
2885                    Style::LineNumber,
2886                );
2887            }
2888        }
2889    }
2890
2891    fn draw_col_separator_end(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2892        match self.theme {
2893            OutputTheme::Ascii => {
2894                self.draw_col_separator_no_space_with_style(
2895                    buffer,
2896                    '|',
2897                    line,
2898                    col,
2899                    Style::LineNumber,
2900                );
2901            }
2902            OutputTheme::Unicode => {
2903                self.draw_col_separator_no_space_with_style(
2904                    buffer,
2905                    '╰',
2906                    line,
2907                    col,
2908                    Style::LineNumber,
2909                );
2910                self.draw_col_separator_no_space_with_style(
2911                    buffer,
2912                    '╴',
2913                    line,
2914                    col + 1,
2915                    Style::LineNumber,
2916                );
2917            }
2918        }
2919    }
2920
2921    fn draw_col_separator_no_space_with_style(
2922        &self,
2923        buffer: &mut StyledBuffer,
2924        chr: char,
2925        line: usize,
2926        col: usize,
2927        style: Style,
2928    ) {
2929        buffer.putc(line, col, chr, style);
2930    }
2931
2932    fn draw_range(
2933        &self,
2934        buffer: &mut StyledBuffer,
2935        symbol: char,
2936        line: usize,
2937        col_from: usize,
2938        col_to: usize,
2939        style: Style,
2940    ) {
2941        for col in col_from..col_to {
2942            buffer.putc(line, col, symbol, style);
2943        }
2944    }
2945
2946    fn draw_note_separator(
2947        &self,
2948        buffer: &mut StyledBuffer,
2949        line: usize,
2950        col: usize,
2951        is_cont: bool,
2952    ) {
2953        let chr = match self.theme {
2954            OutputTheme::Ascii => "= ",
2955            OutputTheme::Unicode if is_cont => "├ ",
2956            OutputTheme::Unicode => "╰ ",
2957        };
2958        buffer.puts(line, col, chr, Style::LineNumber);
2959    }
2960
2961    fn draw_multiline_line(
2962        &self,
2963        buffer: &mut StyledBuffer,
2964        line: usize,
2965        offset: usize,
2966        depth: usize,
2967        style: Style,
2968    ) {
2969        let chr = match (style, self.theme) {
2970            (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Ascii) => '|',
2971            (_, OutputTheme::Ascii) => '|',
2972            (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Unicode) => '┃',
2973            (_, OutputTheme::Unicode) => '│',
2974        };
2975        buffer.putc(line, offset + depth - 1, chr, style);
2976    }
2977
2978    fn file_start(&self) -> &'static str {
2979        match self.theme {
2980            OutputTheme::Ascii => "--> ",
2981            OutputTheme::Unicode => " ╭▸ ",
2982        }
2983    }
2984
2985    fn secondary_file_start(&self) -> &'static str {
2986        match self.theme {
2987            OutputTheme::Ascii => "::: ",
2988            OutputTheme::Unicode => " ⸬ ",
2989        }
2990    }
2991
2992    fn diff(&self) -> char {
2993        match self.theme {
2994            OutputTheme::Ascii => '~',
2995            OutputTheme::Unicode => '±',
2996        }
2997    }
2998
2999    fn draw_line_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
3000        let (column, dots) = match self.theme {
3001            OutputTheme::Ascii => (0, "..."),
3002            OutputTheme::Unicode => (col - 2, "‡"),
3003        };
3004        buffer.puts(line, column, dots, Style::LineNumber);
3005    }
3006
3007    fn margin(&self) -> &'static str {
3008        match self.theme {
3009            OutputTheme::Ascii => "...",
3010            OutputTheme::Unicode => "…",
3011        }
3012    }
3013}
3014
3015#[derive(Debug, Clone, Copy)]
3016struct UnderlineParts {
3017    style: Style,
3018    underline: char,
3019    label_start: char,
3020    vertical_text_line: char,
3021    multiline_vertical: char,
3022    multiline_horizontal: char,
3023    multiline_whole_line: char,
3024    multiline_start_down: char,
3025    bottom_right: char,
3026    top_left: char,
3027    top_right_flat: char,
3028    bottom_left: char,
3029    multiline_end_up: char,
3030    multiline_end_same_line: char,
3031    multiline_bottom_right_with_text: char,
3032}
3033
3034#[derive(Clone, Copy, Debug)]
3035enum DisplaySuggestion {
3036    Underline,
3037    Diff,
3038    None,
3039    Add,
3040}
3041
3042impl FileWithAnnotatedLines {
3043    /// Preprocess all the annotations so that they are grouped by file and by line number
3044    /// This helps us quickly iterate over the whole message (including secondary file spans)
3045    pub(crate) fn collect_annotations(
3046        emitter: &dyn Emitter,
3047        args: &FluentArgs<'_>,
3048        msp: &MultiSpan,
3049    ) -> Vec<FileWithAnnotatedLines> {
3050        fn add_annotation_to_file(
3051            file_vec: &mut Vec<FileWithAnnotatedLines>,
3052            file: Arc<SourceFile>,
3053            line_index: usize,
3054            ann: Annotation,
3055        ) {
3056            for slot in file_vec.iter_mut() {
3057                // Look through each of our files for the one we're adding to
3058                if slot.file.name == file.name {
3059                    // See if we already have a line for it
3060                    for line_slot in &mut slot.lines {
3061                        if line_slot.line_index == line_index {
3062                            line_slot.annotations.push(ann);
3063                            return;
3064                        }
3065                    }
3066                    // We don't have a line yet, create one
3067                    slot.lines.push(Line { line_index, annotations: vec![ann] });
3068                    slot.lines.sort();
3069                    return;
3070                }
3071            }
3072            // This is the first time we're seeing the file
3073            file_vec.push(FileWithAnnotatedLines {
3074                file,
3075                lines: vec![Line { line_index, annotations: vec![ann] }],
3076                multiline_depth: 0,
3077            });
3078        }
3079
3080        let mut output = vec![];
3081        let mut multiline_annotations = vec![];
3082
3083        if let Some(sm) = emitter.source_map() {
3084            for SpanLabel { span, is_primary, label } in msp.span_labels() {
3085                // If we don't have a useful span, pick the primary span if that exists.
3086                // Worst case we'll just print an error at the top of the main file.
3087                let span = match (span.is_dummy(), msp.primary_span()) {
3088                    (_, None) | (false, _) => span,
3089                    (true, Some(span)) => span,
3090                };
3091
3092                let lo = sm.lookup_char_pos(span.lo());
3093                let mut hi = sm.lookup_char_pos(span.hi());
3094
3095                // Watch out for "empty spans". If we get a span like 6..6, we
3096                // want to just display a `^` at 6, so convert that to
3097                // 6..7. This is degenerate input, but it's best to degrade
3098                // gracefully -- and the parser likes to supply a span like
3099                // that for EOF, in particular.
3100
3101                if lo.col_display == hi.col_display && lo.line == hi.line {
3102                    hi.col_display += 1;
3103                }
3104
3105                let label = label.as_ref().map(|m| {
3106                    normalize_whitespace(
3107                        &emitter.translate_message(m, args).map_err(Report::new).unwrap(),
3108                    )
3109                });
3110
3111                if lo.line != hi.line {
3112                    let ml = MultilineAnnotation {
3113                        depth: 1,
3114                        line_start: lo.line,
3115                        line_end: hi.line,
3116                        start_col: AnnotationColumn::from_loc(&lo),
3117                        end_col: AnnotationColumn::from_loc(&hi),
3118                        is_primary,
3119                        label,
3120                        overlaps_exactly: false,
3121                    };
3122                    multiline_annotations.push((lo.file, ml));
3123                } else {
3124                    let ann = Annotation {
3125                        start_col: AnnotationColumn::from_loc(&lo),
3126                        end_col: AnnotationColumn::from_loc(&hi),
3127                        is_primary,
3128                        label,
3129                        annotation_type: AnnotationType::Singleline,
3130                    };
3131                    add_annotation_to_file(&mut output, lo.file, lo.line, ann);
3132                };
3133            }
3134        }
3135
3136        // Find overlapping multiline annotations, put them at different depths
3137        multiline_annotations.sort_by_key(|(_, ml)| (ml.line_start, usize::MAX - ml.line_end));
3138        for (_, ann) in multiline_annotations.clone() {
3139            for (_, a) in multiline_annotations.iter_mut() {
3140                // Move all other multiline annotations overlapping with this one
3141                // one level to the right.
3142                if !(ann.same_span(a))
3143                    && num_overlap(ann.line_start, ann.line_end, a.line_start, a.line_end, true)
3144                {
3145                    a.increase_depth();
3146                } else if ann.same_span(a) && &ann != a {
3147                    a.overlaps_exactly = true;
3148                } else {
3149                    break;
3150                }
3151            }
3152        }
3153
3154        let mut max_depth = 0; // max overlapping multiline spans
3155        for (_, ann) in &multiline_annotations {
3156            max_depth = max(max_depth, ann.depth);
3157        }
3158        // Change order of multispan depth to minimize the number of overlaps in the ASCII art.
3159        for (_, a) in multiline_annotations.iter_mut() {
3160            a.depth = max_depth - a.depth + 1;
3161        }
3162        for (file, ann) in multiline_annotations {
3163            let mut end_ann = ann.as_end();
3164            if !ann.overlaps_exactly {
3165                // avoid output like
3166                //
3167                //  |        foo(
3168                //  |   _____^
3169                //  |  |_____|
3170                //  | ||         bar,
3171                //  | ||     );
3172                //  | ||      ^
3173                //  | ||______|
3174                //  |  |______foo
3175                //  |         baz
3176                //
3177                // and instead get
3178                //
3179                //  |       foo(
3180                //  |  _____^
3181                //  | |         bar,
3182                //  | |     );
3183                //  | |      ^
3184                //  | |      |
3185                //  | |______foo
3186                //  |        baz
3187                add_annotation_to_file(
3188                    &mut output,
3189                    Arc::clone(&file),
3190                    ann.line_start,
3191                    ann.as_start(),
3192                );
3193                // 4 is the minimum vertical length of a multiline span when presented: two lines
3194                // of code and two lines of underline. This is not true for the special case where
3195                // the beginning doesn't have an underline, but the current logic seems to be
3196                // working correctly.
3197                let middle = min(ann.line_start + 4, ann.line_end);
3198                // We'll show up to 4 lines past the beginning of the multispan start.
3199                // We will *not* include the tail of lines that are only whitespace, a comment or
3200                // a bare delimiter.
3201                let filter = |s: &str| {
3202                    let s = s.trim();
3203                    // Consider comments as empty, but don't consider docstrings to be empty.
3204                    !(s.starts_with("//") && !(s.starts_with("///") || s.starts_with("//!")))
3205                        // Consider lines with nothing but whitespace, a single delimiter as empty.
3206                        && !["", "{", "}", "(", ")", "[", "]"].contains(&s)
3207                };
3208                let until = (ann.line_start..middle)
3209                    .rev()
3210                    .filter_map(|line| file.get_line(line - 1).map(|s| (line + 1, s)))
3211                    .find(|(_, s)| filter(s))
3212                    .map(|(line, _)| line)
3213                    .unwrap_or(ann.line_start);
3214                for line in ann.line_start + 1..until {
3215                    // Every `|` that joins the beginning of the span (`___^`) to the end (`|__^`).
3216                    add_annotation_to_file(&mut output, Arc::clone(&file), line, ann.as_line());
3217                }
3218                let line_end = ann.line_end - 1;
3219                let end_is_empty = file.get_line(line_end - 1).is_some_and(|s| !filter(&s));
3220                if middle < line_end && !end_is_empty {
3221                    add_annotation_to_file(&mut output, Arc::clone(&file), line_end, ann.as_line());
3222                }
3223            } else {
3224                end_ann.annotation_type = AnnotationType::Singleline;
3225            }
3226            add_annotation_to_file(&mut output, file, ann.line_end, end_ann);
3227        }
3228        for file_vec in output.iter_mut() {
3229            file_vec.multiline_depth = max_depth;
3230        }
3231        output
3232    }
3233}
3234
3235// instead of taking the String length or dividing by 10 while > 0, we multiply a limit by 10 until
3236// we're higher. If the loop isn't exited by the `return`, the last multiplication will wrap, which
3237// is OK, because while we cannot fit a higher power of 10 in a usize, the loop will end anyway.
3238// This is also why we need the max number of decimal digits within a `usize`.
3239fn num_decimal_digits(num: usize) -> usize {
3240    #[cfg(target_pointer_width = "64")]
3241    const MAX_DIGITS: usize = 20;
3242
3243    #[cfg(target_pointer_width = "32")]
3244    const MAX_DIGITS: usize = 10;
3245
3246    #[cfg(target_pointer_width = "16")]
3247    const MAX_DIGITS: usize = 5;
3248
3249    let mut lim = 10;
3250    for num_digits in 1..MAX_DIGITS {
3251        if num < lim {
3252            return num_digits;
3253        }
3254        lim = lim.wrapping_mul(10);
3255    }
3256    MAX_DIGITS
3257}
3258
3259// We replace some characters so the CLI output is always consistent and underlines aligned.
3260// Keep the following list in sync with `rustc_span::char_width`.
3261const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
3262    // In terminals without Unicode support the following will be garbled, but in *all* terminals
3263    // the underlying codepoint will be as well. We could gate this replacement behind a "unicode
3264    // support" gate.
3265    ('\0', "␀"),
3266    ('\u{0001}', "␁"),
3267    ('\u{0002}', "␂"),
3268    ('\u{0003}', "␃"),
3269    ('\u{0004}', "␄"),
3270    ('\u{0005}', "␅"),
3271    ('\u{0006}', "␆"),
3272    ('\u{0007}', "␇"),
3273    ('\u{0008}', "␈"),
3274    ('\t', "    "), // We do our own tab replacement
3275    ('\u{000b}', "␋"),
3276    ('\u{000c}', "␌"),
3277    ('\u{000d}', "␍"),
3278    ('\u{000e}', "␎"),
3279    ('\u{000f}', "␏"),
3280    ('\u{0010}', "␐"),
3281    ('\u{0011}', "␑"),
3282    ('\u{0012}', "␒"),
3283    ('\u{0013}', "␓"),
3284    ('\u{0014}', "␔"),
3285    ('\u{0015}', "␕"),
3286    ('\u{0016}', "␖"),
3287    ('\u{0017}', "␗"),
3288    ('\u{0018}', "␘"),
3289    ('\u{0019}', "␙"),
3290    ('\u{001a}', "␚"),
3291    ('\u{001b}', "␛"),
3292    ('\u{001c}', "␜"),
3293    ('\u{001d}', "␝"),
3294    ('\u{001e}', "␞"),
3295    ('\u{001f}', "␟"),
3296    ('\u{007f}', "␡"),
3297    ('\u{200d}', ""), // Replace ZWJ for consistent terminal output of grapheme clusters.
3298    ('\u{202a}', "�"), // The following unicode text flow control characters are inconsistently
3299    ('\u{202b}', "�"), // supported across CLIs and can cause confusion due to the bytes on disk
3300    ('\u{202c}', "�"), // not corresponding to the visible source code, so we replace them always.
3301    ('\u{202d}', "�"),
3302    ('\u{202e}', "�"),
3303    ('\u{2066}', "�"),
3304    ('\u{2067}', "�"),
3305    ('\u{2068}', "�"),
3306    ('\u{2069}', "�"),
3307];
3308
3309fn normalize_whitespace(s: &str) -> String {
3310    const {
3311        let mut i = 1;
3312        while i < OUTPUT_REPLACEMENTS.len() {
3313            assert!(
3314                OUTPUT_REPLACEMENTS[i - 1].0 < OUTPUT_REPLACEMENTS[i].0,
3315                "The OUTPUT_REPLACEMENTS array must be sorted (for binary search to work) \
3316                and must contain no duplicate entries"
3317            );
3318            i += 1;
3319        }
3320    }
3321    // Scan the input string for a character in the ordered table above.
3322    // If it's present, replace it with its alternative string (it can be more than 1 char!).
3323    // Otherwise, retain the input char.
3324    s.chars().fold(String::with_capacity(s.len()), |mut s, c| {
3325        match OUTPUT_REPLACEMENTS.binary_search_by_key(&c, |(k, _)| *k) {
3326            Ok(i) => s.push_str(OUTPUT_REPLACEMENTS[i].1),
3327            _ => s.push(c),
3328        }
3329        s
3330    })
3331}
3332
3333fn num_overlap(
3334    a_start: usize,
3335    a_end: usize,
3336    b_start: usize,
3337    b_end: usize,
3338    inclusive: bool,
3339) -> bool {
3340    let extra = if inclusive { 1 } else { 0 };
3341    (b_start..b_end + extra).contains(&a_start) || (a_start..a_end + extra).contains(&b_start)
3342}
3343
3344fn overlaps(a1: &Annotation, a2: &Annotation, padding: usize) -> bool {
3345    num_overlap(
3346        a1.start_col.display,
3347        a1.end_col.display + padding,
3348        a2.start_col.display,
3349        a2.end_col.display,
3350        false,
3351    )
3352}
3353
3354fn emit_to_destination(
3355    rendered_buffer: &[Vec<StyledString>],
3356    lvl: &Level,
3357    dst: &mut Destination,
3358    short_message: bool,
3359) -> io::Result<()> {
3360    use crate::lock;
3361
3362    // In order to prevent error message interleaving, where multiple error lines get intermixed
3363    // when multiple compiler processes error simultaneously, we emit errors with additional
3364    // steps.
3365    //
3366    // On Unix systems, we write into a buffered terminal rather than directly to a terminal. When
3367    // the .flush() is called we take the buffer created from the buffered writes and write it at
3368    // one shot. Because the Unix systems use ANSI for the colors, which is a text-based styling
3369    // scheme, this buffered approach works and maintains the styling.
3370    //
3371    // On Windows, styling happens through calls to a terminal API. This prevents us from using the
3372    // same buffering approach. Instead, we use a global Windows mutex, which we acquire long
3373    // enough to output the full error message, then we release.
3374    let _buffer_lock = lock::acquire_global_lock("rustc_errors");
3375    for (pos, line) in rendered_buffer.iter().enumerate() {
3376        for part in line {
3377            let style = part.style.color_spec(*lvl);
3378            dst.set_color(&style)?;
3379            write!(dst, "{}", part.text)?;
3380            dst.reset()?;
3381        }
3382        if !short_message && (!lvl.is_failure_note() || pos != rendered_buffer.len() - 1) {
3383            writeln!(dst)?;
3384        }
3385    }
3386    dst.flush()?;
3387    Ok(())
3388}
3389
3390pub type Destination = Box<dyn WriteColor + Send>;
3391
3392struct Buffy {
3393    buffer_writer: BufferWriter,
3394    buffer: Buffer,
3395}
3396
3397impl Write for Buffy {
3398    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
3399        self.buffer.write(buf)
3400    }
3401
3402    fn flush(&mut self) -> io::Result<()> {
3403        self.buffer_writer.print(&self.buffer)?;
3404        self.buffer.clear();
3405        Ok(())
3406    }
3407}
3408
3409impl Drop for Buffy {
3410    fn drop(&mut self) {
3411        if !self.buffer.is_empty() {
3412            self.flush().unwrap();
3413            panic!("buffers need to be flushed in order to print their contents");
3414        }
3415    }
3416}
3417
3418impl WriteColor for Buffy {
3419    fn supports_color(&self) -> bool {
3420        self.buffer.supports_color()
3421    }
3422
3423    fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
3424        self.buffer.set_color(spec)
3425    }
3426
3427    fn reset(&mut self) -> io::Result<()> {
3428        self.buffer.reset()
3429    }
3430}
3431
3432pub fn stderr_destination(color: ColorConfig) -> Destination {
3433    let choice = color.to_color_choice();
3434    // On Windows we'll be performing global synchronization on the entire
3435    // system for emitting rustc errors, so there's no need to buffer
3436    // anything.
3437    //
3438    // On non-Windows we rely on the atomicity of `write` to ensure errors
3439    // don't get all jumbled up.
3440    if cfg!(windows) {
3441        Box::new(StandardStream::stderr(choice))
3442    } else {
3443        let buffer_writer = BufferWriter::stderr(choice);
3444        let buffer = buffer_writer.buffer();
3445        Box::new(Buffy { buffer_writer, buffer })
3446    }
3447}
3448
3449/// On Windows, BRIGHT_BLUE is hard to read on black. Use cyan instead.
3450///
3451/// See #36178.
3452const BRIGHT_BLUE: Color = if cfg!(windows) { Color::Cyan } else { Color::Blue };
3453
3454impl Style {
3455    fn color_spec(&self, lvl: Level) -> ColorSpec {
3456        let mut spec = ColorSpec::new();
3457        match self {
3458            Style::Addition => {
3459                spec.set_fg(Some(Color::Green)).set_intense(true);
3460            }
3461            Style::Removal => {
3462                spec.set_fg(Some(Color::Red)).set_intense(true);
3463            }
3464            Style::LineAndColumn => {}
3465            Style::LineNumber => {
3466                spec.set_bold(true);
3467                spec.set_intense(true);
3468                spec.set_fg(Some(BRIGHT_BLUE));
3469            }
3470            Style::Quotation => {}
3471            Style::MainHeaderMsg => {
3472                spec.set_bold(true);
3473                if cfg!(windows) {
3474                    spec.set_intense(true).set_fg(Some(Color::White));
3475                }
3476            }
3477            Style::UnderlinePrimary | Style::LabelPrimary => {
3478                spec = lvl.color();
3479                spec.set_bold(true);
3480            }
3481            Style::UnderlineSecondary | Style::LabelSecondary => {
3482                spec.set_bold(true).set_intense(true);
3483                spec.set_fg(Some(BRIGHT_BLUE));
3484            }
3485            Style::HeaderMsg | Style::NoStyle => {}
3486            Style::Level(lvl) => {
3487                spec = lvl.color();
3488                spec.set_bold(true);
3489            }
3490            Style::Highlight => {
3491                spec.set_bold(true).set_fg(Some(Color::Magenta));
3492            }
3493        }
3494        spec
3495    }
3496}
3497
3498/// Whether the original and suggested code are the same.
3499pub fn is_different(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
3500    let found = match sm.span_to_snippet(sp) {
3501        Ok(snippet) => snippet,
3502        Err(e) => {
3503            warn!(error = ?e, "Invalid span {:?}", sp);
3504            return true;
3505        }
3506    };
3507    found != suggested
3508}
3509
3510/// Whether the original and suggested code are visually similar enough to warrant extra wording.
3511pub fn is_case_difference(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
3512    // FIXME: this should probably be extended to also account for `FO0` → `FOO` and unicode.
3513    let found = match sm.span_to_snippet(sp) {
3514        Ok(snippet) => snippet,
3515        Err(e) => {
3516            warn!(error = ?e, "Invalid span {:?}", sp);
3517            return false;
3518        }
3519    };
3520    let ascii_confusables = &['c', 'f', 'i', 'k', 'o', 's', 'u', 'v', 'w', 'x', 'y', 'z'];
3521    // All the chars that differ in capitalization are confusable (above):
3522    let confusable = iter::zip(found.chars(), suggested.chars())
3523        .filter(|(f, s)| f != s)
3524        .all(|(f, s)| (ascii_confusables.contains(&f) || ascii_confusables.contains(&s)));
3525    confusable && found.to_lowercase() == suggested.to_lowercase()
3526            // FIXME: We sometimes suggest the same thing we already have, which is a
3527            //        bug, but be defensive against that here.
3528            && found != suggested
3529}
3530
3531pub(crate) fn should_show_source_code(
3532    ignored_directories: &[String],
3533    sm: &SourceMap,
3534    file: &SourceFile,
3535) -> bool {
3536    if !sm.ensure_source_file_source_present(file) {
3537        return false;
3538    }
3539
3540    let FileName::Real(name) = &file.name else { return true };
3541    name.local_path()
3542        .map(|path| ignored_directories.iter().all(|dir| !path.starts_with(dir)))
3543        .unwrap_or(true)
3544}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy