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