diff --git a/Cargo.lock b/Cargo.lock index 8333560119640..9dc507f156622 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3865,7 +3865,6 @@ dependencies = [ "rustc_fluent_macro", "rustc_hashes", "rustc_index", - "rustc_lexer", "rustc_lint_defs", "rustc_macros", "rustc_serialize", diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 5dee64b42f73f..43b2cd6829100 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -1534,10 +1534,11 @@ fn report_ice( using_internal_features: &AtomicBool, ) { let translator = default_translator(); - let emitter = Box::new(rustc_errors::emitter::HumanEmitter::new( - stderr_destination(rustc_errors::ColorConfig::Auto), - translator, - )); + let emitter = + Box::new(rustc_errors::annotate_snippet_emitter_writer::AnnotateSnippetEmitter::new( + stderr_destination(rustc_errors::ColorConfig::Auto), + translator, + )); let dcx = rustc_errors::DiagCtxt::new(emitter); let dcx = dcx.handle(); diff --git a/compiler/rustc_errors/Cargo.toml b/compiler/rustc_errors/Cargo.toml index 6c5a1740a9a6c..81eecca3199f4 100644 --- a/compiler/rustc_errors/Cargo.toml +++ b/compiler/rustc_errors/Cargo.toml @@ -17,7 +17,6 @@ rustc_error_messages = { path = "../rustc_error_messages" } rustc_fluent_macro = { path = "../rustc_fluent_macro" } rustc_hashes = { path = "../rustc_hashes" } rustc_index = { path = "../rustc_index" } -rustc_lexer = { path = "../rustc_lexer" } rustc_lint_defs = { path = "../rustc_lint_defs" } rustc_macros = { path = "../rustc_macros" } rustc_serialize = { path = "../rustc_serialize" } diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index 4365ceaff22d4..a9e81354fc6aa 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -15,10 +15,9 @@ use rustc_span::source_map::Spanned; use rustc_span::{DUMMY_SP, Span, Symbol}; use tracing::debug; -use crate::snippet::Style; use crate::{ CodeSuggestion, DiagCtxtHandle, DiagMessage, ErrCode, ErrorGuaranteed, ExplicitBug, Level, - MultiSpan, StashKey, SubdiagMessage, Substitution, SubstitutionPart, SuggestionStyle, + MultiSpan, StashKey, Style, SubdiagMessage, Substitution, SubstitutionPart, SuggestionStyle, Suggestions, }; diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs index 2e41f74ee25d8..3433db1e07042 100644 --- a/compiler/rustc_errors/src/emitter.rs +++ b/compiler/rustc_errors/src/emitter.rs @@ -8,42 +8,29 @@ //! The output types are defined in `rustc_session::config::ErrorOutputType`. use std::borrow::Cow; -use std::cmp::{Reverse, max, min}; use std::error::Report; use std::io::prelude::*; use std::io::{self, IsTerminal}; use std::iter; use std::path::Path; -use std::sync::Arc; use anstream::{AutoStream, ColorChoice}; use anstyle::{AnsiColor, Effects}; -use derive_setters::Setters; -use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; -use rustc_data_structures::sync::{DynSend, IntoDynSyncSend}; -use rustc_error_messages::{FluentArgs, SpanLabel}; -use rustc_lexer; -use rustc_lint_defs::pluralize; +use rustc_data_structures::fx::FxIndexSet; +use rustc_data_structures::sync::DynSend; +use rustc_error_messages::FluentArgs; use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::source_map::SourceMap; -use rustc_span::{FileLines, FileName, SourceFile, Span, char_width, str_width}; -use tracing::{debug, instrument, trace, warn}; +use rustc_span::{FileName, SourceFile, Span}; +use tracing::{debug, warn}; use crate::registry::Registry; -use crate::snippet::{ - Annotation, AnnotationColumn, AnnotationType, Line, MultilineAnnotation, Style, StyledString, -}; -use crate::styled_buffer::StyledBuffer; use crate::timings::TimingRecord; -use crate::translation::{Translator, to_fluent_args}; +use crate::translation::Translator; use crate::{ - CodeSuggestion, DiagInner, DiagMessage, ErrCode, Level, MultiSpan, Subdiag, - SubstitutionHighlight, SuggestionStyle, TerminalUrl, + CodeSuggestion, DiagInner, DiagMessage, Level, MultiSpan, Style, Subdiag, SuggestionStyle, }; -/// Default column width, used in tests and when terminal dimensions cannot be determined. -const DEFAULT_COLUMN_WIDTH: usize = 140; - /// Describes the way the content of the `rendered` field of the json output is generated #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct HumanReadableErrorType { @@ -57,119 +44,11 @@ impl HumanReadableErrorType { } } -#[derive(Clone, Copy, Debug)] -struct Margin { - /// The available whitespace in the left that can be consumed when centering. - pub whitespace_left: usize, - /// The column of the beginning of leftmost span. - pub span_left: usize, - /// The column of the end of rightmost span. - pub span_right: usize, - /// The beginning of the line to be displayed. - pub computed_left: usize, - /// The end of the line to be displayed. - pub computed_right: usize, - /// The current width of the terminal. Uses value of `DEFAULT_COLUMN_WIDTH` constant by default - /// and in tests. - pub column_width: usize, - /// The end column of a span label, including the span. Doesn't account for labels not in the - /// same line as the span. - pub label_right: usize, -} - -impl Margin { - fn new( - whitespace_left: usize, - span_left: usize, - span_right: usize, - label_right: usize, - column_width: usize, - max_line_len: usize, - ) -> Self { - // The 6 is padding to give a bit of room for `...` when displaying: - // ``` - // error: message - // --> file.rs:16:58 - // | - // 16 | ... fn foo(self) -> Self::Bar { - // | ^^^^^^^^^ - // ``` - - let mut m = Margin { - whitespace_left: whitespace_left.saturating_sub(6), - span_left: span_left.saturating_sub(6), - span_right: span_right + 6, - computed_left: 0, - computed_right: 0, - column_width, - label_right: label_right + 6, - }; - m.compute(max_line_len); - m - } - - fn was_cut_left(&self) -> bool { - self.computed_left > 0 - } - - fn compute(&mut self, max_line_len: usize) { - // When there's a lot of whitespace (>20), we want to trim it as it is useless. - // FIXME: this doesn't account for '\t', but to do so correctly we need to perform that - // calculation later, right before printing in order to be accurate with both unicode - // handling and trimming of long lines. - self.computed_left = if self.whitespace_left > 20 { - self.whitespace_left - 16 // We want some padding. - } else { - 0 - }; - // We want to show as much as possible, max_line_len is the rightmost boundary for the - // relevant code. - self.computed_right = max(max_line_len, self.computed_left); - - if self.computed_right - self.computed_left > self.column_width { - // Trimming only whitespace isn't enough, let's get craftier. - if self.label_right - self.whitespace_left <= self.column_width { - // Attempt to fit the code window only trimming whitespace. - self.computed_left = self.whitespace_left; - self.computed_right = self.computed_left + self.column_width; - } else if self.label_right - self.span_left <= self.column_width { - // Attempt to fit the code window considering only the spans and labels. - let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2; - self.computed_left = self.span_left.saturating_sub(padding_left); - self.computed_right = self.computed_left + self.column_width; - } else if self.span_right - self.span_left <= self.column_width { - // Attempt to fit the code window considering the spans and labels plus padding. - let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2; - self.computed_left = self.span_left.saturating_sub(padding_left); - self.computed_right = self.computed_left + self.column_width; - } else { - // Mostly give up but still don't show the full line. - self.computed_left = self.span_left; - self.computed_right = self.span_right; - } - } - } - - fn left(&self, line_len: usize) -> usize { - min(self.computed_left, line_len) - } - - fn right(&self, line_len: usize) -> usize { - if line_len.saturating_sub(self.computed_left) <= self.column_width { - line_len - } else { - min(line_len, self.computed_right) - } - } -} - pub enum TimingEvent { Start, End, } -const ANONYMIZED_LINE_NUM: &str = "LL"; - pub type DynEmitter = dyn Emitter + DynSend; /// Emitter trait for emitting errors and other structured information. @@ -490,48 +369,6 @@ pub trait Emitter { } } -impl Emitter for HumanEmitter { - fn source_map(&self) -> Option<&SourceMap> { - self.sm.as_deref() - } - - fn emit_diagnostic(&mut self, mut diag: DiagInner, _registry: &Registry) { - let fluent_args = to_fluent_args(diag.args.iter()); - - if self.track_diagnostics && diag.span.has_primary_spans() && !diag.span.is_dummy() { - diag.children.insert(0, diag.emitted_at_sub_diag()); - } - - let mut suggestions = diag.suggestions.unwrap_tag(); - self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args); - - self.fix_multispans_in_extern_macros_and_render_macro_backtrace( - &mut diag.span, - &mut diag.children, - &diag.level, - self.macro_backtrace, - ); - - self.emit_messages_default( - &diag.level, - &diag.messages, - &fluent_args, - &diag.code, - &diag.span, - &diag.children, - &suggestions, - ); - } - - fn should_show_explain(&self) -> bool { - !self.short_message - } - - fn translator(&self) -> &Translator { - &self.translator - } -} - /// An emitter that adds a note to each diagnostic. pub struct EmitterWithNote { pub emitter: Box, @@ -604,2703 +441,6 @@ pub enum OutputTheme { Unicode, } -/// Handles the writing of `HumanReadableErrorType` -#[derive(Setters)] -pub struct HumanEmitter { - #[setters(skip)] - dst: IntoDynSyncSend, - sm: Option>, - #[setters(skip)] - translator: Translator, - short_message: bool, - ui_testing: bool, - ignored_directories_in_source_blocks: Vec, - diagnostic_width: Option, - - macro_backtrace: bool, - track_diagnostics: bool, - terminal_url: TerminalUrl, - theme: OutputTheme, -} - -#[derive(Debug)] -pub(crate) struct FileWithAnnotatedLines { - pub(crate) file: Arc, - pub(crate) lines: Vec, - multiline_depth: usize, -} - -impl HumanEmitter { - pub fn new(dst: Destination, translator: Translator) -> HumanEmitter { - HumanEmitter { - dst: IntoDynSyncSend(dst), - sm: None, - translator, - short_message: false, - ui_testing: false, - ignored_directories_in_source_blocks: Vec::new(), - diagnostic_width: None, - macro_backtrace: false, - track_diagnostics: false, - terminal_url: TerminalUrl::No, - theme: OutputTheme::Ascii, - } - } - - fn maybe_anonymized(&self, line_num: usize) -> Cow<'static, str> { - if self.ui_testing { - Cow::Borrowed(ANONYMIZED_LINE_NUM) - } else { - Cow::Owned(line_num.to_string()) - } - } - - fn draw_line( - &self, - buffer: &mut StyledBuffer, - source_string: &str, - line_index: usize, - line_offset: usize, - width_offset: usize, - code_offset: usize, - margin: Margin, - ) -> usize { - let line_len = source_string.len(); - // Create the source line we will highlight. - let left = margin.left(line_len); - let right = margin.right(line_len); - // FIXME: The following code looks fishy. See #132860. - // On long lines, we strip the source line, accounting for unicode. - let code: String = source_string - .chars() - .enumerate() - .skip_while(|(i, _)| *i < left) - .take_while(|(i, _)| *i < right) - .map(|(_, c)| c) - .collect(); - let code = normalize_whitespace(&code); - let was_cut_right = - source_string.chars().enumerate().skip_while(|(i, _)| *i < right).next().is_some(); - buffer.puts(line_offset, code_offset, &code, Style::Quotation); - let placeholder = self.margin(); - if margin.was_cut_left() { - // We have stripped some code/whitespace from the beginning, make it clear. - buffer.puts(line_offset, code_offset, placeholder, Style::LineNumber); - } - if was_cut_right { - let padding = str_width(placeholder); - // We have stripped some code after the rightmost span end, make it clear we did so. - buffer.puts( - line_offset, - code_offset + str_width(&code) - padding, - placeholder, - Style::LineNumber, - ); - } - self.draw_line_num(buffer, line_index, line_offset, width_offset - 3); - self.draw_col_separator_no_space(buffer, line_offset, width_offset - 2); - left - } - - #[instrument(level = "trace", skip(self), ret)] - fn render_source_line( - &self, - buffer: &mut StyledBuffer, - file: Arc, - line: &Line, - width_offset: usize, - code_offset: usize, - margin: Margin, - close_window: bool, - ) -> Vec<(usize, Style)> { - // Draw: - // - // LL | ... code ... - // | ^^-^ span label - // | | - // | secondary span label - // - // ^^ ^ ^^^ ^^^^ ^^^ we don't care about code too far to the right of a span, we trim it - // | | | | - // | | | actual code found in your source code and the spans we use to mark it - // | | when there's too much wasted space to the left, trim it - // | vertical divider between the column number and the code - // column number - - if line.line_index == 0 { - return Vec::new(); - } - - let Some(source_string) = file.get_line(line.line_index - 1) else { - return Vec::new(); - }; - trace!(?source_string); - - let line_offset = buffer.num_lines(); - - // Left trim. - // FIXME: This looks fishy. See #132860. - let left = self.draw_line( - buffer, - &source_string, - line.line_index, - line_offset, - width_offset, - code_offset, - margin, - ); - - // Special case when there's only one annotation involved, it is the start of a multiline - // span and there's no text at the beginning of the code line. Instead of doing the whole - // graph: - // - // 2 | fn foo() { - // | _^ - // 3 | | - // 4 | | } - // | |_^ test - // - // we simplify the output to: - // - // 2 | / fn foo() { - // 3 | | - // 4 | | } - // | |_^ test - let mut buffer_ops = vec![]; - let mut annotations = vec![]; - let mut short_start = true; - for ann in &line.annotations { - if let AnnotationType::MultilineStart(depth) = ann.annotation_type { - if source_string.chars().take(ann.start_col.file).all(|c| c.is_whitespace()) { - let uline = self.underline(ann.is_primary); - let chr = uline.multiline_whole_line; - annotations.push((depth, uline.style)); - buffer_ops.push((line_offset, width_offset + depth - 1, chr, uline.style)); - } else { - short_start = false; - break; - } - } else if let AnnotationType::MultilineLine(_) = ann.annotation_type { - } else { - short_start = false; - break; - } - } - if short_start { - for (y, x, c, s) in buffer_ops { - buffer.putc(y, x, c, s); - } - return annotations; - } - - // We want to display like this: - // - // vec.push(vec.pop().unwrap()); - // --- ^^^ - previous borrow ends here - // | | - // | error occurs here - // previous borrow of `vec` occurs here - // - // But there are some weird edge cases to be aware of: - // - // vec.push(vec.pop().unwrap()); - // -------- - previous borrow ends here - // || - // |this makes no sense - // previous borrow of `vec` occurs here - // - // For this reason, we group the lines into "highlight lines" - // and "annotations lines", where the highlight lines have the `^`. - - // Sort the annotations by (start, end col) - // The labels are reversed, sort and then reversed again. - // Consider a list of annotations (A1, A2, C1, C2, B1, B2) where - // the letter signifies the span. Here we are only sorting by the - // span and hence, the order of the elements with the same span will - // not change. On reversing the ordering (|a, b| but b.cmp(a)), you get - // (C1, C2, B1, B2, A1, A2). All the elements with the same span are - // still ordered first to last, but all the elements with different - // spans are ordered by their spans in last to first order. Last to - // first order is important, because the jiggly lines and | are on - // the left, so the rightmost span needs to be rendered first, - // otherwise the lines would end up needing to go over a message. - - let mut annotations = line.annotations.clone(); - annotations.sort_by_key(|a| Reverse(a.start_col)); - - // First, figure out where each label will be positioned. - // - // In the case where you have the following annotations: - // - // vec.push(vec.pop().unwrap()); - // -------- - previous borrow ends here [C] - // || - // |this makes no sense [B] - // previous borrow of `vec` occurs here [A] - // - // `annotations_position` will hold [(2, A), (1, B), (0, C)]. - // - // We try, when possible, to stick the rightmost annotation at the end - // of the highlight line: - // - // vec.push(vec.pop().unwrap()); - // --- --- - previous borrow ends here - // - // But sometimes that's not possible because one of the other - // annotations overlaps it. For example, from the test - // `span_overlap_label`, we have the following annotations - // (written on distinct lines for clarity): - // - // fn foo(x: u32) { - // -------------- - // - - // - // In this case, we can't stick the rightmost-most label on - // the highlight line, or we would get: - // - // fn foo(x: u32) { - // -------- x_span - // | - // fn_span - // - // which is totally weird. Instead we want: - // - // fn foo(x: u32) { - // -------------- - // | | - // | x_span - // fn_span - // - // which is...less weird, at least. In fact, in general, if - // the rightmost span overlaps with any other span, we should - // use the "hang below" version, so we can at least make it - // clear where the span *starts*. There's an exception for this - // logic, when the labels do not have a message: - // - // fn foo(x: u32) { - // -------------- - // | - // x_span - // - // instead of: - // - // fn foo(x: u32) { - // -------------- - // | | - // | x_span - // - // - let mut overlap = vec![false; annotations.len()]; - let mut annotations_position = vec![]; - let mut line_len: usize = 0; - let mut p = 0; - for (i, annotation) in annotations.iter().enumerate() { - for (j, next) in annotations.iter().enumerate() { - if overlaps(next, annotation, 0) && j > i { - overlap[i] = true; - overlap[j] = true; - } - if overlaps(next, annotation, 0) // This label overlaps with another one and both - && annotation.has_label() // take space (they have text and are not - && j > i // multiline lines). - && p == 0 - // We're currently on the first line, move the label one line down - { - // If we're overlapping with an un-labelled annotation with the same span - // we can just merge them in the output - if next.start_col == annotation.start_col - && next.end_col == annotation.end_col - && !next.has_label() - { - continue; - } - - // This annotation needs a new line in the output. - p += 1; - break; - } - } - annotations_position.push((p, annotation)); - for (j, next) in annotations.iter().enumerate() { - if j > i { - let l = next.label.as_ref().map_or(0, |label| label.len() + 2); - if (overlaps(next, annotation, l) // Do not allow two labels to be in the same - // line if they overlap including padding, to - // avoid situations like: - // - // fn foo(x: u32) { - // -------^------ - // | | - // fn_spanx_span - // - && annotation.has_label() // Both labels must have some text, otherwise - && next.has_label()) // they are not overlapping. - // Do not add a new line if this annotation - // or the next are vertical line placeholders. - || (annotation.takes_space() // If either this or the next annotation is - && next.has_label()) // multiline start/end, move it to a new line - || (annotation.has_label() // so as not to overlap the horizontal lines. - && next.takes_space()) - || (annotation.takes_space() && next.takes_space()) - || (overlaps(next, annotation, l) - && next.end_col <= annotation.end_col - && next.has_label() - && p == 0) - // Avoid #42595. - { - // This annotation needs a new line in the output. - p += 1; - break; - } - } - } - line_len = max(line_len, p); - } - - if line_len != 0 { - line_len += 1; - } - - // If there are no annotations or the only annotations on this line are - // MultilineLine, then there's only code being shown, stop processing. - if line.annotations.iter().all(|a| a.is_line()) { - return vec![]; - } - - if annotations_position - .iter() - .all(|(_, ann)| matches!(ann.annotation_type, AnnotationType::MultilineStart(_))) - && let Some(max_pos) = annotations_position.iter().map(|(pos, _)| *pos).max() - { - // Special case the following, so that we minimize overlapping multiline spans. - // - // 3 │ X0 Y0 Z0 - // │ ┏━━━━━┛ │ │ < We are writing these lines - // │ ┃┌───────┘ │ < by reverting the "depth" of - // │ ┃│┌─────────┘ < their multiline spans. - // 4 │ ┃││ X1 Y1 Z1 - // 5 │ ┃││ X2 Y2 Z2 - // │ ┃│└────╿──│──┘ `Z` label - // │ ┃└─────│──┤ - // │ ┗━━━━━━┥ `Y` is a good letter too - // ╰╴ `X` is a good letter - for (pos, _) in &mut annotations_position { - *pos = max_pos - *pos; - } - // We know then that we don't need an additional line for the span label, saving us - // one line of vertical space. - line_len = line_len.saturating_sub(1); - } - - // Write the column separator. - // - // After this we will have: - // - // 2 | fn foo() { - // | - // | - // | - // 3 | - // 4 | } - // | - for pos in 0..=line_len { - self.draw_col_separator_no_space(buffer, line_offset + pos + 1, width_offset - 2); - } - if close_window { - self.draw_col_separator_end(buffer, line_offset + line_len + 1, width_offset - 2); - } - - // Write the horizontal lines for multiline annotations - // (only the first and last lines need this). - // - // After this we will have: - // - // 2 | fn foo() { - // | __________ - // | - // | - // 3 | - // 4 | } - // | _ - for &(pos, annotation) in &annotations_position { - let underline = self.underline(annotation.is_primary); - let pos = pos + 1; - match annotation.annotation_type { - AnnotationType::MultilineStart(depth) | AnnotationType::MultilineEnd(depth) => { - let pre: usize = source_string - .chars() - .take(annotation.start_col.file) - .skip(left) - .map(|c| char_width(c)) - .sum(); - self.draw_range( - buffer, - underline.multiline_horizontal, - line_offset + pos, - width_offset + depth, - code_offset + pre, - underline.style, - ); - } - _ => {} - } - } - - // Write the vertical lines for labels that are on a different line as the underline. - // - // After this we will have: - // - // 2 | fn foo() { - // | __________ - // | | | - // | | - // 3 | | - // 4 | | } - // | |_ - for &(pos, annotation) in &annotations_position { - let underline = self.underline(annotation.is_primary); - let pos = pos + 1; - - let code_offset = code_offset - + source_string - .chars() - .take(annotation.start_col.file) - .skip(left) - .map(|c| char_width(c)) - .sum::(); - if pos > 1 && (annotation.has_label() || annotation.takes_space()) { - for p in line_offset + 1..=line_offset + pos { - buffer.putc( - p, - code_offset, - match annotation.annotation_type { - AnnotationType::MultilineLine(_) => underline.multiline_vertical, - _ => underline.vertical_text_line, - }, - underline.style, - ); - } - if let AnnotationType::MultilineStart(_) = annotation.annotation_type { - buffer.putc( - line_offset + pos, - code_offset, - underline.bottom_right, - underline.style, - ); - } - if let AnnotationType::MultilineEnd(_) = annotation.annotation_type - && annotation.has_label() - { - buffer.putc( - line_offset + pos, - code_offset, - underline.multiline_bottom_right_with_text, - underline.style, - ); - } - } - match annotation.annotation_type { - AnnotationType::MultilineStart(depth) => { - buffer.putc( - line_offset + pos, - width_offset + depth - 1, - underline.top_left, - underline.style, - ); - for p in line_offset + pos + 1..line_offset + line_len + 2 { - buffer.putc( - p, - width_offset + depth - 1, - underline.multiline_vertical, - underline.style, - ); - } - } - AnnotationType::MultilineEnd(depth) => { - for p in line_offset..line_offset + pos { - buffer.putc( - p, - width_offset + depth - 1, - underline.multiline_vertical, - underline.style, - ); - } - buffer.putc( - line_offset + pos, - width_offset + depth - 1, - underline.bottom_left, - underline.style, - ); - } - _ => (), - } - } - - // Write the labels on the annotations that actually have a label. - // - // After this we will have: - // - // 2 | fn foo() { - // | __________ - // | | - // | something about `foo` - // 3 | - // 4 | } - // | _ test - for &(pos, annotation) in &annotations_position { - let style = - if annotation.is_primary { Style::LabelPrimary } else { Style::LabelSecondary }; - let (pos, col) = if pos == 0 { - let pre: usize = source_string - .chars() - .take(annotation.end_col.file) - .skip(left) - .map(|c| char_width(c)) - .sum(); - if annotation.end_col.file == 0 { - (pos + 1, (pre + 2)) - } else { - let pad = if annotation.end_col.file - annotation.start_col.file == 0 { - 2 - } else { - 1 - }; - (pos + 1, (pre + pad)) - } - } else { - let pre: usize = source_string - .chars() - .take(annotation.start_col.file) - .skip(left) - .map(|c| char_width(c)) - .sum(); - (pos + 2, pre) - }; - if let Some(ref label) = annotation.label { - buffer.puts(line_offset + pos, code_offset + col, label, style); - } - } - - // Sort from biggest span to smallest span so that smaller spans are - // represented in the output: - // - // x | fn foo() - // | ^^^---^^ - // | | | - // | | something about `foo` - // | something about `fn foo()` - annotations_position.sort_by_key(|(_, ann)| { - // Decreasing order. When annotations share the same length, prefer `Primary`. - (Reverse(ann.len()), ann.is_primary) - }); - - // Write the underlines. - // - // After this we will have: - // - // 2 | fn foo() { - // | ____-_____^ - // | | - // | something about `foo` - // 3 | - // 4 | } - // | _^ test - for &(pos, annotation) in &annotations_position { - let uline = self.underline(annotation.is_primary); - let width = annotation.end_col.file - annotation.start_col.file; - let previous: String = - source_string.chars().take(annotation.start_col.file).skip(left).collect(); - let underlined: String = - source_string.chars().skip(annotation.start_col.file).take(width).collect(); - debug!(?previous, ?underlined); - let code_offset = code_offset - + source_string - .chars() - .take(annotation.start_col.file) - .skip(left) - .map(|c| char_width(c)) - .sum::(); - let ann_width: usize = source_string - .chars() - .skip(annotation.start_col.file) - .take(width) - .map(|c| char_width(c)) - .sum(); - let ann_width = if ann_width == 0 - && matches!(annotation.annotation_type, AnnotationType::Singleline) - { - 1 - } else { - ann_width - }; - for p in 0..ann_width { - // The default span label underline. - buffer.putc(line_offset + 1, code_offset + p, uline.underline, uline.style); - } - - if pos == 0 - && matches!( - annotation.annotation_type, - AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_) - ) - { - // The beginning of a multiline span with its leftward moving line on the same line. - buffer.putc( - line_offset + 1, - code_offset, - match annotation.annotation_type { - AnnotationType::MultilineStart(_) => uline.top_right_flat, - AnnotationType::MultilineEnd(_) => uline.multiline_end_same_line, - _ => panic!("unexpected annotation type: {annotation:?}"), - }, - uline.style, - ); - } else if pos != 0 - && matches!( - annotation.annotation_type, - AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_) - ) - { - // The beginning of a multiline span with its leftward moving line on another line, - // so we start going down first. - buffer.putc( - line_offset + 1, - code_offset, - match annotation.annotation_type { - AnnotationType::MultilineStart(_) => uline.multiline_start_down, - AnnotationType::MultilineEnd(_) => uline.multiline_end_up, - _ => panic!("unexpected annotation type: {annotation:?}"), - }, - uline.style, - ); - } else if pos != 0 && annotation.has_label() { - // The beginning of a span label with an actual label, we'll point down. - buffer.putc(line_offset + 1, code_offset, uline.label_start, uline.style); - } - } - - // We look for individual *long* spans, and we trim the *middle*, so that we render - // LL | ...= [0, 0, 0, ..., 0, 0]; - // | ^^^^^^^^^^...^^^^^^^ expected `&[u8]`, found `[{integer}; 1680]` - for (i, (_pos, annotation)) in annotations_position.iter().enumerate() { - // Skip cases where multiple spans overlap each other. - if overlap[i] { - continue; - }; - let AnnotationType::Singleline = annotation.annotation_type else { continue }; - let width = annotation.end_col.display - annotation.start_col.display; - if width > margin.column_width * 2 && width > 10 { - // If the terminal is *too* small, we keep at least a tiny bit of the span for - // display. - let pad = max(margin.column_width / 3, 5); - // Code line - buffer.replace( - line_offset, - annotation.start_col.file + pad, - annotation.end_col.file - pad, - self.margin(), - ); - // Underline line - buffer.replace( - line_offset + 1, - annotation.start_col.file + pad, - annotation.end_col.file - pad, - self.margin(), - ); - } - } - annotations_position - .iter() - .filter_map(|&(_, annotation)| match annotation.annotation_type { - AnnotationType::MultilineStart(p) | AnnotationType::MultilineEnd(p) => { - let style = if annotation.is_primary { - Style::LabelPrimary - } else { - Style::LabelSecondary - }; - Some((p, style)) - } - _ => None, - }) - .collect::>() - } - - fn get_multispan_max_line_num(&mut self, msp: &MultiSpan) -> usize { - let Some(ref sm) = self.sm else { - return 0; - }; - - let will_be_emitted = |span: Span| { - !span.is_dummy() && { - let file = sm.lookup_source_file(span.hi()); - should_show_source_code(&self.ignored_directories_in_source_blocks, sm, &file) - } - }; - - let mut max = 0; - for primary_span in msp.primary_spans() { - if will_be_emitted(*primary_span) { - let hi = sm.lookup_char_pos(primary_span.hi()); - max = (hi.line).max(max); - } - } - if !self.short_message { - for span_label in msp.span_labels() { - if will_be_emitted(span_label.span) { - let hi = sm.lookup_char_pos(span_label.span.hi()); - max = (hi.line).max(max); - } - } - } - - max - } - - fn get_max_line_num(&mut self, span: &MultiSpan, children: &[Subdiag]) -> usize { - let primary = self.get_multispan_max_line_num(span); - children - .iter() - .map(|sub| self.get_multispan_max_line_num(&sub.span)) - .max() - .unwrap_or(0) - .max(primary) - } - - /// Adds a left margin to every line but the first, given a padding length and the label being - /// displayed, keeping the provided highlighting. - fn msgs_to_buffer( - &self, - buffer: &mut StyledBuffer, - msgs: &[(DiagMessage, Style)], - args: &FluentArgs<'_>, - padding: usize, - label: &str, - override_style: Option