summaryrefslogtreecommitdiffstats
path: root/third_party/rust/codespan-reporting/src/term
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/rust/codespan-reporting/src/term
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/codespan-reporting/src/term')
-rw-r--r--third_party/rust/codespan-reporting/src/term/config.rs321
-rw-r--r--third_party/rust/codespan-reporting/src/term/renderer.rs1020
-rw-r--r--third_party/rust/codespan-reporting/src/term/views.rs478
3 files changed, 1819 insertions, 0 deletions
diff --git a/third_party/rust/codespan-reporting/src/term/config.rs b/third_party/rust/codespan-reporting/src/term/config.rs
new file mode 100644
index 0000000000..c92a6b6047
--- /dev/null
+++ b/third_party/rust/codespan-reporting/src/term/config.rs
@@ -0,0 +1,321 @@
+use termcolor::{Color, ColorSpec};
+
+use crate::diagnostic::{LabelStyle, Severity};
+
+/// Configures how a diagnostic is rendered.
+#[derive(Clone, Debug)]
+pub struct Config {
+ /// The display style to use when rendering diagnostics.
+ /// Defaults to: [`DisplayStyle::Rich`].
+ ///
+ /// [`DisplayStyle::Rich`]: DisplayStyle::Rich
+ pub display_style: DisplayStyle,
+ /// Column width of tabs.
+ /// Defaults to: `4`.
+ pub tab_width: usize,
+ /// Styles to use when rendering the diagnostic.
+ pub styles: Styles,
+ /// Characters to use when rendering the diagnostic.
+ pub chars: Chars,
+ /// The minimum number of lines to be shown after the line on which a multiline [`Label`] begins.
+ ///
+ /// Defaults to: `3`.
+ ///
+ /// [`Label`]: crate::diagnostic::Label
+ pub start_context_lines: usize,
+ /// The minimum number of lines to be shown before the line on which a multiline [`Label`] ends.
+ ///
+ /// Defaults to: `1`.
+ ///
+ /// [`Label`]: crate::diagnostic::Label
+ pub end_context_lines: usize,
+}
+
+impl Default for Config {
+ fn default() -> Config {
+ Config {
+ display_style: DisplayStyle::Rich,
+ tab_width: 4,
+ styles: Styles::default(),
+ chars: Chars::default(),
+ start_context_lines: 3,
+ end_context_lines: 1,
+ }
+ }
+}
+
+/// The display style to use when rendering diagnostics.
+#[derive(Clone, Debug)]
+pub enum DisplayStyle {
+ /// Output a richly formatted diagnostic, with source code previews.
+ ///
+ /// ```text
+ /// error[E0001]: unexpected type in `+` application
+ /// ┌─ test:2:9
+ /// │
+ /// 2 │ (+ test "")
+ /// │ ^^ expected `Int` but found `String`
+ /// │
+ /// = expected type `Int`
+ /// found type `String`
+ ///
+ /// error[E0002]: Bad config found
+ ///
+ /// ```
+ Rich,
+ /// Output a condensed diagnostic, with a line number, severity, message and notes (if any).
+ ///
+ /// ```text
+ /// test:2:9: error[E0001]: unexpected type in `+` application
+ /// = expected type `Int`
+ /// found type `String`
+ ///
+ /// error[E0002]: Bad config found
+ /// ```
+ Medium,
+ /// Output a short diagnostic, with a line number, severity, and message.
+ ///
+ /// ```text
+ /// test:2:9: error[E0001]: unexpected type in `+` application
+ /// error[E0002]: Bad config found
+ /// ```
+ Short,
+}
+
+/// Styles to use when rendering the diagnostic.
+#[derive(Clone, Debug)]
+pub struct Styles {
+ /// The style to use when rendering bug headers.
+ /// Defaults to `fg:red bold intense`.
+ pub header_bug: ColorSpec,
+ /// The style to use when rendering error headers.
+ /// Defaults to `fg:red bold intense`.
+ pub header_error: ColorSpec,
+ /// The style to use when rendering warning headers.
+ /// Defaults to `fg:yellow bold intense`.
+ pub header_warning: ColorSpec,
+ /// The style to use when rendering note headers.
+ /// Defaults to `fg:green bold intense`.
+ pub header_note: ColorSpec,
+ /// The style to use when rendering help headers.
+ /// Defaults to `fg:cyan bold intense`.
+ pub header_help: ColorSpec,
+ /// The style to use when the main diagnostic message.
+ /// Defaults to `bold intense`.
+ pub header_message: ColorSpec,
+
+ /// The style to use when rendering bug labels.
+ /// Defaults to `fg:red`.
+ pub primary_label_bug: ColorSpec,
+ /// The style to use when rendering error labels.
+ /// Defaults to `fg:red`.
+ pub primary_label_error: ColorSpec,
+ /// The style to use when rendering warning labels.
+ /// Defaults to `fg:yellow`.
+ pub primary_label_warning: ColorSpec,
+ /// The style to use when rendering note labels.
+ /// Defaults to `fg:green`.
+ pub primary_label_note: ColorSpec,
+ /// The style to use when rendering help labels.
+ /// Defaults to `fg:cyan`.
+ pub primary_label_help: ColorSpec,
+ /// The style to use when rendering secondary labels.
+ /// Defaults `fg:blue` (or `fg:cyan` on windows).
+ pub secondary_label: ColorSpec,
+
+ /// The style to use when rendering the line numbers.
+ /// Defaults `fg:blue` (or `fg:cyan` on windows).
+ pub line_number: ColorSpec,
+ /// The style to use when rendering the source code borders.
+ /// Defaults `fg:blue` (or `fg:cyan` on windows).
+ pub source_border: ColorSpec,
+ /// The style to use when rendering the note bullets.
+ /// Defaults `fg:blue` (or `fg:cyan` on windows).
+ pub note_bullet: ColorSpec,
+}
+
+impl Styles {
+ /// The style used to mark a header at a given severity.
+ pub fn header(&self, severity: Severity) -> &ColorSpec {
+ match severity {
+ Severity::Bug => &self.header_bug,
+ Severity::Error => &self.header_error,
+ Severity::Warning => &self.header_warning,
+ Severity::Note => &self.header_note,
+ Severity::Help => &self.header_help,
+ }
+ }
+
+ /// The style used to mark a primary or secondary label at a given severity.
+ pub fn label(&self, severity: Severity, label_style: LabelStyle) -> &ColorSpec {
+ match (label_style, severity) {
+ (LabelStyle::Primary, Severity::Bug) => &self.primary_label_bug,
+ (LabelStyle::Primary, Severity::Error) => &self.primary_label_error,
+ (LabelStyle::Primary, Severity::Warning) => &self.primary_label_warning,
+ (LabelStyle::Primary, Severity::Note) => &self.primary_label_note,
+ (LabelStyle::Primary, Severity::Help) => &self.primary_label_help,
+ (LabelStyle::Secondary, _) => &self.secondary_label,
+ }
+ }
+
+ #[doc(hidden)]
+ pub fn with_blue(blue: Color) -> Styles {
+ let header = ColorSpec::new().set_bold(true).set_intense(true).clone();
+
+ Styles {
+ header_bug: header.clone().set_fg(Some(Color::Red)).clone(),
+ header_error: header.clone().set_fg(Some(Color::Red)).clone(),
+ header_warning: header.clone().set_fg(Some(Color::Yellow)).clone(),
+ header_note: header.clone().set_fg(Some(Color::Green)).clone(),
+ header_help: header.clone().set_fg(Some(Color::Cyan)).clone(),
+ header_message: header,
+
+ primary_label_bug: ColorSpec::new().set_fg(Some(Color::Red)).clone(),
+ primary_label_error: ColorSpec::new().set_fg(Some(Color::Red)).clone(),
+ primary_label_warning: ColorSpec::new().set_fg(Some(Color::Yellow)).clone(),
+ primary_label_note: ColorSpec::new().set_fg(Some(Color::Green)).clone(),
+ primary_label_help: ColorSpec::new().set_fg(Some(Color::Cyan)).clone(),
+ secondary_label: ColorSpec::new().set_fg(Some(blue)).clone(),
+
+ line_number: ColorSpec::new().set_fg(Some(blue)).clone(),
+ source_border: ColorSpec::new().set_fg(Some(blue)).clone(),
+ note_bullet: ColorSpec::new().set_fg(Some(blue)).clone(),
+ }
+ }
+}
+
+impl Default for Styles {
+ fn default() -> Styles {
+ // Blue is really difficult to see on the standard windows command line
+ #[cfg(windows)]
+ const BLUE: Color = Color::Cyan;
+ #[cfg(not(windows))]
+ const BLUE: Color = Color::Blue;
+
+ Self::with_blue(BLUE)
+ }
+}
+
+/// Characters to use when rendering the diagnostic.
+///
+/// By using [`Chars::ascii()`] you can switch to an ASCII-only format suitable
+/// for rendering on terminals that do not support box drawing characters.
+#[derive(Clone, Debug)]
+pub struct Chars {
+ /// The characters to use for the top-left border of the snippet.
+ /// Defaults to: `"┌─"` or `"-->"` with [`Chars::ascii()`].
+ pub snippet_start: String,
+ /// The character to use for the left border of the source.
+ /// Defaults to: `'│'` or `'|'` with [`Chars::ascii()`].
+ pub source_border_left: char,
+ /// The character to use for the left border break of the source.
+ /// Defaults to: `'·'` or `'.'` with [`Chars::ascii()`].
+ pub source_border_left_break: char,
+
+ /// The character to use for the note bullet.
+ /// Defaults to: `'='`.
+ pub note_bullet: char,
+
+ /// The character to use for marking a single-line primary label.
+ /// Defaults to: `'^'`.
+ pub single_primary_caret: char,
+ /// The character to use for marking a single-line secondary label.
+ /// Defaults to: `'-'`.
+ pub single_secondary_caret: char,
+
+ /// The character to use for marking the start of a multi-line primary label.
+ /// Defaults to: `'^'`.
+ pub multi_primary_caret_start: char,
+ /// The character to use for marking the end of a multi-line primary label.
+ /// Defaults to: `'^'`.
+ pub multi_primary_caret_end: char,
+ /// The character to use for marking the start of a multi-line secondary label.
+ /// Defaults to: `'\''`.
+ pub multi_secondary_caret_start: char,
+ /// The character to use for marking the end of a multi-line secondary label.
+ /// Defaults to: `'\''`.
+ pub multi_secondary_caret_end: char,
+ /// The character to use for the top-left corner of a multi-line label.
+ /// Defaults to: `'╭'` or `'/'` with [`Chars::ascii()`].
+ pub multi_top_left: char,
+ /// The character to use for the top of a multi-line label.
+ /// Defaults to: `'─'` or `'-'` with [`Chars::ascii()`].
+ pub multi_top: char,
+ /// The character to use for the bottom-left corner of a multi-line label.
+ /// Defaults to: `'╰'` or `'\'` with [`Chars::ascii()`].
+ pub multi_bottom_left: char,
+ /// The character to use when marking the bottom of a multi-line label.
+ /// Defaults to: `'─'` or `'-'` with [`Chars::ascii()`].
+ pub multi_bottom: char,
+ /// The character to use for the left of a multi-line label.
+ /// Defaults to: `'│'` or `'|'` with [`Chars::ascii()`].
+ pub multi_left: char,
+
+ /// The character to use for the left of a pointer underneath a caret.
+ /// Defaults to: `'│'` or `'|'` with [`Chars::ascii()`].
+ pub pointer_left: char,
+}
+
+impl Default for Chars {
+ fn default() -> Chars {
+ Chars::box_drawing()
+ }
+}
+
+impl Chars {
+ /// A character set that uses Unicode box drawing characters.
+ pub fn box_drawing() -> Chars {
+ Chars {
+ snippet_start: "┌─".into(),
+ source_border_left: '│',
+ source_border_left_break: '·',
+
+ note_bullet: '=',
+
+ single_primary_caret: '^',
+ single_secondary_caret: '-',
+
+ multi_primary_caret_start: '^',
+ multi_primary_caret_end: '^',
+ multi_secondary_caret_start: '\'',
+ multi_secondary_caret_end: '\'',
+ multi_top_left: '╭',
+ multi_top: '─',
+ multi_bottom_left: '╰',
+ multi_bottom: '─',
+ multi_left: '│',
+
+ pointer_left: '│',
+ }
+ }
+
+ /// A character set that only uses ASCII characters.
+ ///
+ /// This is useful if your terminal's font does not support box drawing
+ /// characters well and results in output that looks similar to rustc's
+ /// diagnostic output.
+ pub fn ascii() -> Chars {
+ Chars {
+ snippet_start: "-->".into(),
+ source_border_left: '|',
+ source_border_left_break: '.',
+
+ note_bullet: '=',
+
+ single_primary_caret: '^',
+ single_secondary_caret: '-',
+
+ multi_primary_caret_start: '^',
+ multi_primary_caret_end: '^',
+ multi_secondary_caret_start: '\'',
+ multi_secondary_caret_end: '\'',
+ multi_top_left: '/',
+ multi_top: '-',
+ multi_bottom_left: '\\',
+ multi_bottom: '-',
+ multi_left: '|',
+
+ pointer_left: '|',
+ }
+ }
+}
diff --git a/third_party/rust/codespan-reporting/src/term/renderer.rs b/third_party/rust/codespan-reporting/src/term/renderer.rs
new file mode 100644
index 0000000000..eeb8965d25
--- /dev/null
+++ b/third_party/rust/codespan-reporting/src/term/renderer.rs
@@ -0,0 +1,1020 @@
+use std::io::{self, Write};
+use std::ops::Range;
+use termcolor::{ColorSpec, WriteColor};
+
+use crate::diagnostic::{LabelStyle, Severity};
+use crate::files::{Error, Location};
+use crate::term::{Chars, Config, Styles};
+
+/// The 'location focus' of a source code snippet.
+pub struct Locus {
+ /// The user-facing name of the file.
+ pub name: String,
+ /// The location.
+ pub location: Location,
+}
+
+/// Single-line label, with an optional message.
+///
+/// ```text
+/// ^^^^^^^^^ blah blah
+/// ```
+pub type SingleLabel<'diagnostic> = (LabelStyle, Range<usize>, &'diagnostic str);
+
+/// A multi-line label to render.
+///
+/// Locations are relative to the start of where the source code is rendered.
+pub enum MultiLabel<'diagnostic> {
+ /// Multi-line label top.
+ /// The contained value indicates where the label starts.
+ ///
+ /// ```text
+ /// ╭────────────^
+ /// ```
+ ///
+ /// Can also be rendered at the beginning of the line
+ /// if there is only whitespace before the label starts.
+ ///
+ /// /// ```text
+ /// ╭
+ /// ```
+ Top(usize),
+ /// Left vertical labels for multi-line labels.
+ ///
+ /// ```text
+ /// │
+ /// ```
+ Left,
+ /// Multi-line label bottom, with an optional message.
+ /// The first value indicates where the label ends.
+ ///
+ /// ```text
+ /// ╰────────────^ blah blah
+ /// ```
+ Bottom(usize, &'diagnostic str),
+}
+
+#[derive(Copy, Clone)]
+enum VerticalBound {
+ Top,
+ Bottom,
+}
+
+type Underline = (LabelStyle, VerticalBound);
+
+/// A renderer of display list entries.
+///
+/// The following diagram gives an overview of each of the parts of the renderer's output:
+///
+/// ```text
+/// ┌ outer gutter
+/// │ ┌ left border
+/// │ │ ┌ inner gutter
+/// │ │ │ ┌─────────────────────────── source ─────────────────────────────┐
+/// │ │ │ │ │
+/// ┌────────────────────────────────────────────────────────────────────────────
+/// header ── │ error[0001]: oh noes, a cupcake has occurred!
+/// snippet start ── │ ┌─ test:9:0
+/// snippet empty ── │ │
+/// snippet line ── │ 9 │ ╭ Cupcake ipsum dolor. Sit amet marshmallow topping cheesecake
+/// snippet line ── │ 10 │ │ muffin. Halvah croissant candy canes bonbon candy. Apple pie jelly
+/// │ │ ╭─│─────────^
+/// snippet break ── │ · │ │
+/// snippet line ── │ 33 │ │ │ Muffin danish chocolate soufflé pastry icing bonbon oat cake.
+/// snippet line ── │ 34 │ │ │ Powder cake jujubes oat cake. Lemon drops tootsie roll marshmallow
+/// │ │ │ ╰─────────────────────────────^ blah blah
+/// snippet break ── │ · │
+/// snippet line ── │ 38 │ │ Brownie lemon drops chocolate jelly-o candy canes. Danish marzipan
+/// snippet line ── │ 39 │ │ jujubes soufflé carrot cake marshmallow tiramisu caramels candy canes.
+/// │ │ │ ^^^^^^^^^^^^^^^^^^^ -------------------- blah blah
+/// │ │ │ │
+/// │ │ │ blah blah
+/// │ │ │ note: this is a note
+/// snippet line ── │ 40 │ │ Fruitcake jelly-o danish toffee. Tootsie roll pastry cheesecake
+/// snippet line ── │ 41 │ │ soufflé marzipan. Chocolate bar oat cake jujubes lollipop pastry
+/// snippet line ── │ 42 │ │ cupcake. Candy canes cupcake toffee gingerbread candy canes muffin
+/// │ │ │ ^^^^^^^^^^^^^^^^^^ blah blah
+/// │ │ ╰──────────^ blah blah
+/// snippet break ── │ ·
+/// snippet line ── │ 82 │ gingerbread toffee chupa chups chupa chups jelly-o cotton candy.
+/// │ │ ^^^^^^ ------- blah blah
+/// snippet empty ── │ │
+/// snippet note ── │ = blah blah
+/// snippet note ── │ = blah blah blah
+/// │ blah blah
+/// snippet note ── │ = blah blah blah
+/// │ blah blah
+/// empty ── │
+/// ```
+///
+/// Filler text from http://www.cupcakeipsum.com
+pub struct Renderer<'writer, 'config> {
+ writer: &'writer mut dyn WriteColor,
+ config: &'config Config,
+}
+
+impl<'writer, 'config> Renderer<'writer, 'config> {
+ /// Construct a renderer from the given writer and config.
+ pub fn new(
+ writer: &'writer mut dyn WriteColor,
+ config: &'config Config,
+ ) -> Renderer<'writer, 'config> {
+ Renderer { writer, config }
+ }
+
+ fn chars(&self) -> &'config Chars {
+ &self.config.chars
+ }
+
+ fn styles(&self) -> &'config Styles {
+ &self.config.styles
+ }
+
+ /// Diagnostic header, with severity, code, and message.
+ ///
+ /// ```text
+ /// error[E0001]: unexpected type in `+` application
+ /// ```
+ pub fn render_header(
+ &mut self,
+ locus: Option<&Locus>,
+ severity: Severity,
+ code: Option<&str>,
+ message: &str,
+ ) -> Result<(), Error> {
+ // Write locus
+ //
+ // ```text
+ // test:2:9:
+ // ```
+ if let Some(locus) = locus {
+ self.snippet_locus(locus)?;
+ write!(self, ": ")?;
+ }
+
+ // Write severity name
+ //
+ // ```text
+ // error
+ // ```
+ self.set_color(self.styles().header(severity))?;
+ match severity {
+ Severity::Bug => write!(self, "bug")?,
+ Severity::Error => write!(self, "error")?,
+ Severity::Warning => write!(self, "warning")?,
+ Severity::Help => write!(self, "help")?,
+ Severity::Note => write!(self, "note")?,
+ }
+
+ // Write error code
+ //
+ // ```text
+ // [E0001]
+ // ```
+ if let Some(code) = &code.filter(|code| !code.is_empty()) {
+ write!(self, "[{}]", code)?;
+ }
+
+ // Write diagnostic message
+ //
+ // ```text
+ // : unexpected type in `+` application
+ // ```
+ self.set_color(&self.styles().header_message)?;
+ write!(self, ": {}", message)?;
+ self.reset()?;
+
+ writeln!(self)?;
+
+ Ok(())
+ }
+
+ /// Empty line.
+ pub fn render_empty(&mut self) -> Result<(), Error> {
+ writeln!(self)?;
+ Ok(())
+ }
+
+ /// Top left border and locus.
+ ///
+ /// ```text
+ /// ┌─ test:2:9
+ /// ```
+ pub fn render_snippet_start(
+ &mut self,
+ outer_padding: usize,
+ locus: &Locus,
+ ) -> Result<(), Error> {
+ self.outer_gutter(outer_padding)?;
+
+ self.set_color(&self.styles().source_border)?;
+ write!(self, "{}", self.chars().snippet_start)?;
+ self.reset()?;
+
+ write!(self, " ")?;
+ self.snippet_locus(&locus)?;
+
+ writeln!(self)?;
+
+ Ok(())
+ }
+
+ /// A line of source code.
+ ///
+ /// ```text
+ /// 10 │ │ muffin. Halvah croissant candy canes bonbon candy. Apple pie jelly
+ /// │ ╭─│─────────^
+ /// ```
+ pub fn render_snippet_source(
+ &mut self,
+ outer_padding: usize,
+ line_number: usize,
+ source: &str,
+ severity: Severity,
+ single_labels: &[SingleLabel<'_>],
+ num_multi_labels: usize,
+ multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
+ ) -> Result<(), Error> {
+ // Trim trailing newlines, linefeeds, and null chars from source, if they exist.
+ // FIXME: Use the number of trimmed placeholders when rendering single line carets
+ let source = source.trim_end_matches(['\n', '\r', '\0'].as_ref());
+
+ // Write source line
+ //
+ // ```text
+ // 10 │ │ muffin. Halvah croissant candy canes bonbon candy. Apple pie jelly
+ // ```
+ {
+ // Write outer gutter (with line number) and border
+ self.outer_gutter_number(line_number, outer_padding)?;
+ self.border_left()?;
+
+ // Write inner gutter (with multi-line continuations on the left if necessary)
+ let mut multi_labels_iter = multi_labels.iter().peekable();
+ for label_column in 0..num_multi_labels {
+ match multi_labels_iter.peek() {
+ Some((label_index, label_style, label)) if *label_index == label_column => {
+ match label {
+ MultiLabel::Top(start)
+ if *start <= source.len() - source.trim_start().len() =>
+ {
+ self.label_multi_top_left(severity, *label_style)?;
+ }
+ MultiLabel::Top(..) => self.inner_gutter_space()?,
+ MultiLabel::Left | MultiLabel::Bottom(..) => {
+ self.label_multi_left(severity, *label_style, None)?;
+ }
+ }
+ multi_labels_iter.next();
+ }
+ Some((_, _, _)) | None => self.inner_gutter_space()?,
+ }
+ }
+
+ // Write source text
+ write!(self, " ")?;
+ let mut in_primary = false;
+ for (metrics, ch) in self.char_metrics(source.char_indices()) {
+ let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8());
+
+ // Check if we are overlapping a primary label
+ let is_primary = single_labels.iter().any(|(ls, range, _)| {
+ *ls == LabelStyle::Primary && is_overlapping(range, &column_range)
+ }) || multi_labels.iter().any(|(_, ls, label)| {
+ *ls == LabelStyle::Primary
+ && match label {
+ MultiLabel::Top(start) => column_range.start >= *start,
+ MultiLabel::Left => true,
+ MultiLabel::Bottom(start, _) => column_range.end <= *start,
+ }
+ });
+
+ // Set the source color if we are in a primary label
+ if is_primary && !in_primary {
+ self.set_color(self.styles().label(severity, LabelStyle::Primary))?;
+ in_primary = true;
+ } else if !is_primary && in_primary {
+ self.reset()?;
+ in_primary = false;
+ }
+
+ match ch {
+ '\t' => (0..metrics.unicode_width).try_for_each(|_| write!(self, " "))?,
+ _ => write!(self, "{}", ch)?,
+ }
+ }
+ if in_primary {
+ self.reset()?;
+ }
+ writeln!(self)?;
+ }
+
+ // Write single labels underneath source
+ //
+ // ```text
+ // │ - ---- ^^^ second mutable borrow occurs here
+ // │ │ │
+ // │ │ first mutable borrow occurs here
+ // │ first borrow later used by call
+ // │ help: some help here
+ // ```
+ if !single_labels.is_empty() {
+ // Our plan is as follows:
+ //
+ // 1. Do an initial scan to find:
+ // - The number of non-empty messages.
+ // - The right-most start and end positions of labels.
+ // - A candidate for a trailing label (where the label's message
+ // is printed to the left of the caret).
+ // 2. Check if the trailing label candidate overlaps another label -
+ // if so we print it underneath the carets with the other labels.
+ // 3. Print a line of carets, and (possibly) the trailing message
+ // to the left.
+ // 4. Print vertical lines pointing to the carets, and the messages
+ // for those carets.
+ //
+ // We try our best avoid introducing new dynamic allocations,
+ // instead preferring to iterate over the labels multiple times. It
+ // is unclear what the performance tradeoffs are however, so further
+ // investigation may be required.
+
+ // The number of non-empty messages to print.
+ let mut num_messages = 0;
+ // The right-most start position, eg:
+ //
+ // ```text
+ // -^^^^---- ^^^^^^^
+ // │
+ // right-most start position
+ // ```
+ let mut max_label_start = 0;
+ // The right-most end position, eg:
+ //
+ // ```text
+ // -^^^^---- ^^^^^^^
+ // │
+ // right-most end position
+ // ```
+ let mut max_label_end = 0;
+ // A trailing message, eg:
+ //
+ // ```text
+ // ^^^ second mutable borrow occurs here
+ // ```
+ let mut trailing_label = None;
+
+ for (label_index, label) in single_labels.iter().enumerate() {
+ let (_, range, message) = label;
+ if !message.is_empty() {
+ num_messages += 1;
+ }
+ max_label_start = std::cmp::max(max_label_start, range.start);
+ max_label_end = std::cmp::max(max_label_end, range.end);
+ // This is a candidate for the trailing label, so let's record it.
+ if range.end == max_label_end {
+ if message.is_empty() {
+ trailing_label = None;
+ } else {
+ trailing_label = Some((label_index, label));
+ }
+ }
+ }
+ if let Some((trailing_label_index, (_, trailing_range, _))) = trailing_label {
+ // Check to see if the trailing label candidate overlaps any of
+ // the other labels on the current line.
+ if single_labels
+ .iter()
+ .enumerate()
+ .filter(|(label_index, _)| *label_index != trailing_label_index)
+ .any(|(_, (_, range, _))| is_overlapping(trailing_range, range))
+ {
+ // If it does, we'll instead want to render it below the
+ // carets along with the other hanging labels.
+ trailing_label = None;
+ }
+ }
+
+ // Write a line of carets
+ //
+ // ```text
+ // │ ^^^^^^ -------^^^^^^^^^-------^^^^^----- ^^^^ trailing label message
+ // ```
+ self.outer_gutter(outer_padding)?;
+ self.border_left()?;
+ self.inner_gutter(severity, num_multi_labels, multi_labels)?;
+ write!(self, " ")?;
+
+ let mut previous_label_style = None;
+ let placeholder_metrics = Metrics {
+ byte_index: source.len(),
+ unicode_width: 1,
+ };
+ for (metrics, ch) in self
+ .char_metrics(source.char_indices())
+ // Add a placeholder source column at the end to allow for
+ // printing carets at the end of lines, eg:
+ //
+ // ```text
+ // 1 │ Hello world!
+ // │ ^
+ // ```
+ .chain(std::iter::once((placeholder_metrics, '\0')))
+ {
+ // Find the current label style at this column
+ let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8());
+ let current_label_style = single_labels
+ .iter()
+ .filter(|(_, range, _)| is_overlapping(range, &column_range))
+ .map(|(label_style, _, _)| *label_style)
+ .max_by_key(label_priority_key);
+
+ // Update writer style if necessary
+ if previous_label_style != current_label_style {
+ match current_label_style {
+ None => self.reset()?,
+ Some(label_style) => {
+ self.set_color(self.styles().label(severity, label_style))?;
+ }
+ }
+ }
+
+ let caret_ch = match current_label_style {
+ Some(LabelStyle::Primary) => Some(self.chars().single_primary_caret),
+ Some(LabelStyle::Secondary) => Some(self.chars().single_secondary_caret),
+ // Only print padding if we are before the end of the last single line caret
+ None if metrics.byte_index < max_label_end => Some(' '),
+ None => None,
+ };
+ if let Some(caret_ch) = caret_ch {
+ // FIXME: improve rendering of carets between character boundaries
+ (0..metrics.unicode_width).try_for_each(|_| write!(self, "{}", caret_ch))?;
+ }
+
+ previous_label_style = current_label_style;
+ }
+ // Reset style if it was previously set
+ if previous_label_style.is_some() {
+ self.reset()?;
+ }
+ // Write first trailing label message
+ if let Some((_, (label_style, _, message))) = trailing_label {
+ write!(self, " ")?;
+ self.set_color(self.styles().label(severity, *label_style))?;
+ write!(self, "{}", message)?;
+ self.reset()?;
+ }
+ writeln!(self)?;
+
+ // Write hanging labels pointing to carets
+ //
+ // ```text
+ // │ │ │
+ // │ │ first mutable borrow occurs here
+ // │ first borrow later used by call
+ // │ help: some help here
+ // ```
+ if num_messages > trailing_label.iter().count() {
+ // Write first set of vertical lines before hanging labels
+ //
+ // ```text
+ // │ │ │
+ // ```
+ self.outer_gutter(outer_padding)?;
+ self.border_left()?;
+ self.inner_gutter(severity, num_multi_labels, multi_labels)?;
+ write!(self, " ")?;
+ self.caret_pointers(
+ severity,
+ max_label_start,
+ single_labels,
+ trailing_label,
+ source.char_indices(),
+ )?;
+ writeln!(self)?;
+
+ // Write hanging labels pointing to carets
+ //
+ // ```text
+ // │ │ first mutable borrow occurs here
+ // │ first borrow later used by call
+ // │ help: some help here
+ // ```
+ for (label_style, range, message) in
+ hanging_labels(single_labels, trailing_label).rev()
+ {
+ self.outer_gutter(outer_padding)?;
+ self.border_left()?;
+ self.inner_gutter(severity, num_multi_labels, multi_labels)?;
+ write!(self, " ")?;
+ self.caret_pointers(
+ severity,
+ max_label_start,
+ single_labels,
+ trailing_label,
+ source
+ .char_indices()
+ .take_while(|(byte_index, _)| *byte_index < range.start),
+ )?;
+ self.set_color(self.styles().label(severity, *label_style))?;
+ write!(self, "{}", message)?;
+ self.reset()?;
+ writeln!(self)?;
+ }
+ }
+ }
+
+ // Write top or bottom label carets underneath source
+ //
+ // ```text
+ // │ ╰───│──────────────────^ woops
+ // │ ╭─│─────────^
+ // ```
+ for (multi_label_index, (_, label_style, label)) in multi_labels.iter().enumerate() {
+ let (label_style, range, bottom_message) = match label {
+ MultiLabel::Left => continue, // no label caret needed
+ // no label caret needed if this can be started in front of the line
+ MultiLabel::Top(start) if *start <= source.len() - source.trim_start().len() => {
+ continue
+ }
+ MultiLabel::Top(range) => (*label_style, range, None),
+ MultiLabel::Bottom(range, message) => (*label_style, range, Some(message)),
+ };
+
+ self.outer_gutter(outer_padding)?;
+ self.border_left()?;
+
+ // Write inner gutter.
+ //
+ // ```text
+ // │ ╭─│───│
+ // ```
+ let mut underline = None;
+ let mut multi_labels_iter = multi_labels.iter().enumerate().peekable();
+ for label_column in 0..num_multi_labels {
+ match multi_labels_iter.peek() {
+ Some((i, (label_index, ls, label))) if *label_index == label_column => {
+ match label {
+ MultiLabel::Left => {
+ self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?;
+ }
+ MultiLabel::Top(..) if multi_label_index > *i => {
+ self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?;
+ }
+ MultiLabel::Bottom(..) if multi_label_index < *i => {
+ self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?;
+ }
+ MultiLabel::Top(..) if multi_label_index == *i => {
+ underline = Some((*ls, VerticalBound::Top));
+ self.label_multi_top_left(severity, label_style)?
+ }
+ MultiLabel::Bottom(..) if multi_label_index == *i => {
+ underline = Some((*ls, VerticalBound::Bottom));
+ self.label_multi_bottom_left(severity, label_style)?;
+ }
+ MultiLabel::Top(..) | MultiLabel::Bottom(..) => {
+ self.inner_gutter_column(severity, underline)?;
+ }
+ }
+ multi_labels_iter.next();
+ }
+ Some((_, _)) | None => self.inner_gutter_column(severity, underline)?,
+ }
+ }
+
+ // Finish the top or bottom caret
+ match bottom_message {
+ None => self.label_multi_top_caret(severity, label_style, source, *range)?,
+ Some(message) => {
+ self.label_multi_bottom_caret(severity, label_style, source, *range, message)?
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ /// An empty source line, for providing additional whitespace to source snippets.
+ ///
+ /// ```text
+ /// │ │ │
+ /// ```
+ pub fn render_snippet_empty(
+ &mut self,
+ outer_padding: usize,
+ severity: Severity,
+ num_multi_labels: usize,
+ multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
+ ) -> Result<(), Error> {
+ self.outer_gutter(outer_padding)?;
+ self.border_left()?;
+ self.inner_gutter(severity, num_multi_labels, multi_labels)?;
+ writeln!(self)?;
+ Ok(())
+ }
+
+ /// A broken source line, for labeling skipped sections of source.
+ ///
+ /// ```text
+ /// · │ │
+ /// ```
+ pub fn render_snippet_break(
+ &mut self,
+ outer_padding: usize,
+ severity: Severity,
+ num_multi_labels: usize,
+ multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
+ ) -> Result<(), Error> {
+ self.outer_gutter(outer_padding)?;
+ self.border_left_break()?;
+ self.inner_gutter(severity, num_multi_labels, multi_labels)?;
+ writeln!(self)?;
+ Ok(())
+ }
+
+ /// Additional notes.
+ ///
+ /// ```text
+ /// = expected type `Int`
+ /// found type `String`
+ /// ```
+ pub fn render_snippet_note(
+ &mut self,
+ outer_padding: usize,
+ message: &str,
+ ) -> Result<(), Error> {
+ for (note_line_index, line) in message.lines().enumerate() {
+ self.outer_gutter(outer_padding)?;
+ match note_line_index {
+ 0 => {
+ self.set_color(&self.styles().note_bullet)?;
+ write!(self, "{}", self.chars().note_bullet)?;
+ self.reset()?;
+ }
+ _ => write!(self, " ")?,
+ }
+ // Write line of message
+ writeln!(self, " {}", line)?;
+ }
+
+ Ok(())
+ }
+
+ /// Adds tab-stop aware unicode-width computations to an iterator over
+ /// character indices. Assumes that the character indices begin at the start
+ /// of the line.
+ fn char_metrics(
+ &self,
+ char_indices: impl Iterator<Item = (usize, char)>,
+ ) -> impl Iterator<Item = (Metrics, char)> {
+ use unicode_width::UnicodeWidthChar;
+
+ let tab_width = self.config.tab_width;
+ let mut unicode_column = 0;
+
+ char_indices.map(move |(byte_index, ch)| {
+ let metrics = Metrics {
+ byte_index,
+ unicode_width: match (ch, tab_width) {
+ ('\t', 0) => 0, // Guard divide-by-zero
+ ('\t', _) => tab_width - (unicode_column % tab_width),
+ (ch, _) => ch.width().unwrap_or(0),
+ },
+ };
+ unicode_column += metrics.unicode_width;
+
+ (metrics, ch)
+ })
+ }
+
+ /// Location focus.
+ fn snippet_locus(&mut self, locus: &Locus) -> Result<(), Error> {
+ write!(
+ self,
+ "{name}:{line_number}:{column_number}",
+ name = locus.name,
+ line_number = locus.location.line_number,
+ column_number = locus.location.column_number,
+ )?;
+ Ok(())
+ }
+
+ /// The outer gutter of a source line.
+ fn outer_gutter(&mut self, outer_padding: usize) -> Result<(), Error> {
+ write!(self, "{space: >width$} ", space = "", width = outer_padding)?;
+ Ok(())
+ }
+
+ /// The outer gutter of a source line, with line number.
+ fn outer_gutter_number(
+ &mut self,
+ line_number: usize,
+ outer_padding: usize,
+ ) -> Result<(), Error> {
+ self.set_color(&self.styles().line_number)?;
+ write!(
+ self,
+ "{line_number: >width$}",
+ line_number = line_number,
+ width = outer_padding,
+ )?;
+ self.reset()?;
+ write!(self, " ")?;
+ Ok(())
+ }
+
+ /// The left-hand border of a source line.
+ fn border_left(&mut self) -> Result<(), Error> {
+ self.set_color(&self.styles().source_border)?;
+ write!(self, "{}", self.chars().source_border_left)?;
+ self.reset()?;
+ Ok(())
+ }
+
+ /// The broken left-hand border of a source line.
+ fn border_left_break(&mut self) -> Result<(), Error> {
+ self.set_color(&self.styles().source_border)?;
+ write!(self, "{}", self.chars().source_border_left_break)?;
+ self.reset()?;
+ Ok(())
+ }
+
+ /// Write vertical lines pointing to carets.
+ fn caret_pointers(
+ &mut self,
+ severity: Severity,
+ max_label_start: usize,
+ single_labels: &[SingleLabel<'_>],
+ trailing_label: Option<(usize, &SingleLabel<'_>)>,
+ char_indices: impl Iterator<Item = (usize, char)>,
+ ) -> Result<(), Error> {
+ for (metrics, ch) in self.char_metrics(char_indices) {
+ let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8());
+ let label_style = hanging_labels(single_labels, trailing_label)
+ .filter(|(_, range, _)| column_range.contains(&range.start))
+ .map(|(label_style, _, _)| *label_style)
+ .max_by_key(label_priority_key);
+
+ let mut spaces = match label_style {
+ None => 0..metrics.unicode_width,
+ Some(label_style) => {
+ self.set_color(self.styles().label(severity, label_style))?;
+ write!(self, "{}", self.chars().pointer_left)?;
+ self.reset()?;
+ 1..metrics.unicode_width
+ }
+ };
+ // Only print padding if we are before the end of the last single line caret
+ if metrics.byte_index <= max_label_start {
+ spaces.try_for_each(|_| write!(self, " "))?;
+ }
+ }
+
+ Ok(())
+ }
+
+ /// The left of a multi-line label.
+ ///
+ /// ```text
+ /// │
+ /// ```
+ fn label_multi_left(
+ &mut self,
+ severity: Severity,
+ label_style: LabelStyle,
+ underline: Option<LabelStyle>,
+ ) -> Result<(), Error> {
+ match underline {
+ None => write!(self, " ")?,
+ // Continue an underline horizontally
+ Some(label_style) => {
+ self.set_color(self.styles().label(severity, label_style))?;
+ write!(self, "{}", self.chars().multi_top)?;
+ self.reset()?;
+ }
+ }
+ self.set_color(self.styles().label(severity, label_style))?;
+ write!(self, "{}", self.chars().multi_left)?;
+ self.reset()?;
+ Ok(())
+ }
+
+ /// The top-left of a multi-line label.
+ ///
+ /// ```text
+ /// ╭
+ /// ```
+ fn label_multi_top_left(
+ &mut self,
+ severity: Severity,
+ label_style: LabelStyle,
+ ) -> Result<(), Error> {
+ write!(self, " ")?;
+ self.set_color(self.styles().label(severity, label_style))?;
+ write!(self, "{}", self.chars().multi_top_left)?;
+ self.reset()?;
+ Ok(())
+ }
+
+ /// The bottom left of a multi-line label.
+ ///
+ /// ```text
+ /// ╰
+ /// ```
+ fn label_multi_bottom_left(
+ &mut self,
+ severity: Severity,
+ label_style: LabelStyle,
+ ) -> Result<(), Error> {
+ write!(self, " ")?;
+ self.set_color(self.styles().label(severity, label_style))?;
+ write!(self, "{}", self.chars().multi_bottom_left)?;
+ self.reset()?;
+ Ok(())
+ }
+
+ /// Multi-line label top.
+ ///
+ /// ```text
+ /// ─────────────^
+ /// ```
+ fn label_multi_top_caret(
+ &mut self,
+ severity: Severity,
+ label_style: LabelStyle,
+ source: &str,
+ start: usize,
+ ) -> Result<(), Error> {
+ self.set_color(self.styles().label(severity, label_style))?;
+
+ for (metrics, _) in self
+ .char_metrics(source.char_indices())
+ .take_while(|(metrics, _)| metrics.byte_index < start + 1)
+ {
+ // FIXME: improve rendering of carets between character boundaries
+ (0..metrics.unicode_width)
+ .try_for_each(|_| write!(self, "{}", self.chars().multi_top))?;
+ }
+
+ let caret_start = match label_style {
+ LabelStyle::Primary => self.config.chars.multi_primary_caret_start,
+ LabelStyle::Secondary => self.config.chars.multi_secondary_caret_start,
+ };
+ write!(self, "{}", caret_start)?;
+ self.reset()?;
+ writeln!(self)?;
+ Ok(())
+ }
+
+ /// Multi-line label bottom, with a message.
+ ///
+ /// ```text
+ /// ─────────────^ expected `Int` but found `String`
+ /// ```
+ fn label_multi_bottom_caret(
+ &mut self,
+ severity: Severity,
+ label_style: LabelStyle,
+ source: &str,
+ start: usize,
+ message: &str,
+ ) -> Result<(), Error> {
+ self.set_color(self.styles().label(severity, label_style))?;
+
+ for (metrics, _) in self
+ .char_metrics(source.char_indices())
+ .take_while(|(metrics, _)| metrics.byte_index < start)
+ {
+ // FIXME: improve rendering of carets between character boundaries
+ (0..metrics.unicode_width)
+ .try_for_each(|_| write!(self, "{}", self.chars().multi_bottom))?;
+ }
+
+ let caret_end = match label_style {
+ LabelStyle::Primary => self.config.chars.multi_primary_caret_start,
+ LabelStyle::Secondary => self.config.chars.multi_secondary_caret_start,
+ };
+ write!(self, "{}", caret_end)?;
+ if !message.is_empty() {
+ write!(self, " {}", message)?;
+ }
+ self.reset()?;
+ writeln!(self)?;
+ Ok(())
+ }
+
+ /// Writes an empty gutter space, or continues an underline horizontally.
+ fn inner_gutter_column(
+ &mut self,
+ severity: Severity,
+ underline: Option<Underline>,
+ ) -> Result<(), Error> {
+ match underline {
+ None => self.inner_gutter_space(),
+ Some((label_style, vertical_bound)) => {
+ self.set_color(self.styles().label(severity, label_style))?;
+ let ch = match vertical_bound {
+ VerticalBound::Top => self.config.chars.multi_top,
+ VerticalBound::Bottom => self.config.chars.multi_bottom,
+ };
+ write!(self, "{0}{0}", ch)?;
+ self.reset()?;
+ Ok(())
+ }
+ }
+ }
+
+ /// Writes an empty gutter space.
+ fn inner_gutter_space(&mut self) -> Result<(), Error> {
+ write!(self, " ")?;
+ Ok(())
+ }
+
+ /// Writes an inner gutter, with the left lines if necessary.
+ fn inner_gutter(
+ &mut self,
+ severity: Severity,
+ num_multi_labels: usize,
+ multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
+ ) -> Result<(), Error> {
+ let mut multi_labels_iter = multi_labels.iter().peekable();
+ for label_column in 0..num_multi_labels {
+ match multi_labels_iter.peek() {
+ Some((label_index, ls, label)) if *label_index == label_column => match label {
+ MultiLabel::Left | MultiLabel::Bottom(..) => {
+ self.label_multi_left(severity, *ls, None)?;
+ multi_labels_iter.next();
+ }
+ MultiLabel::Top(..) => {
+ self.inner_gutter_space()?;
+ multi_labels_iter.next();
+ }
+ },
+ Some((_, _, _)) | None => self.inner_gutter_space()?,
+ }
+ }
+
+ Ok(())
+ }
+}
+
+impl<'writer, 'config> Write for Renderer<'writer, 'config> {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.writer.write(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.writer.flush()
+ }
+}
+
+impl<'writer, 'config> WriteColor for Renderer<'writer, 'config> {
+ fn supports_color(&self) -> bool {
+ self.writer.supports_color()
+ }
+
+ fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
+ self.writer.set_color(spec)
+ }
+
+ fn reset(&mut self) -> io::Result<()> {
+ self.writer.reset()
+ }
+
+ fn is_synchronous(&self) -> bool {
+ self.writer.is_synchronous()
+ }
+}
+
+struct Metrics {
+ byte_index: usize,
+ unicode_width: usize,
+}
+
+/// Check if two ranges overlap
+fn is_overlapping(range0: &Range<usize>, range1: &Range<usize>) -> bool {
+ let start = std::cmp::max(range0.start, range1.start);
+ let end = std::cmp::min(range0.end, range1.end);
+ start < end
+}
+
+/// For prioritizing primary labels over secondary labels when rendering carets.
+fn label_priority_key(label_style: &LabelStyle) -> u8 {
+ match label_style {
+ LabelStyle::Secondary => 0,
+ LabelStyle::Primary => 1,
+ }
+}
+
+/// Return an iterator that yields the labels that require hanging messages
+/// rendered underneath them.
+fn hanging_labels<'labels, 'diagnostic>(
+ single_labels: &'labels [SingleLabel<'diagnostic>],
+ trailing_label: Option<(usize, &'labels SingleLabel<'diagnostic>)>,
+) -> impl 'labels + DoubleEndedIterator<Item = &'labels SingleLabel<'diagnostic>> {
+ single_labels
+ .iter()
+ .enumerate()
+ .filter(|(_, (_, _, message))| !message.is_empty())
+ .filter(move |(i, _)| trailing_label.map_or(true, |(j, _)| *i != j))
+ .map(|(_, label)| label)
+}
diff --git a/third_party/rust/codespan-reporting/src/term/views.rs b/third_party/rust/codespan-reporting/src/term/views.rs
new file mode 100644
index 0000000000..f09d9582a6
--- /dev/null
+++ b/third_party/rust/codespan-reporting/src/term/views.rs
@@ -0,0 +1,478 @@
+use std::ops::Range;
+
+use crate::diagnostic::{Diagnostic, LabelStyle};
+use crate::files::{Error, Files, Location};
+use crate::term::renderer::{Locus, MultiLabel, Renderer, SingleLabel};
+use crate::term::Config;
+
+/// Count the number of decimal digits in `n`.
+fn count_digits(mut n: usize) -> usize {
+ let mut count = 0;
+ while n != 0 {
+ count += 1;
+ n /= 10; // remove last digit
+ }
+ count
+}
+
+/// Output a richly formatted diagnostic, with source code previews.
+pub struct RichDiagnostic<'diagnostic, 'config, FileId> {
+ diagnostic: &'diagnostic Diagnostic<FileId>,
+ config: &'config Config,
+}
+
+impl<'diagnostic, 'config, FileId> RichDiagnostic<'diagnostic, 'config, FileId>
+where
+ FileId: Copy + PartialEq,
+{
+ pub fn new(
+ diagnostic: &'diagnostic Diagnostic<FileId>,
+ config: &'config Config,
+ ) -> RichDiagnostic<'diagnostic, 'config, FileId> {
+ RichDiagnostic { diagnostic, config }
+ }
+
+ pub fn render<'files>(
+ &self,
+ files: &'files impl Files<'files, FileId = FileId>,
+ renderer: &mut Renderer<'_, '_>,
+ ) -> Result<(), Error>
+ where
+ FileId: 'files,
+ {
+ use std::collections::BTreeMap;
+
+ struct LabeledFile<'diagnostic, FileId> {
+ file_id: FileId,
+ start: usize,
+ name: String,
+ location: Location,
+ num_multi_labels: usize,
+ lines: BTreeMap<usize, Line<'diagnostic>>,
+ max_label_style: LabelStyle,
+ }
+
+ impl<'diagnostic, FileId> LabeledFile<'diagnostic, FileId> {
+ fn get_or_insert_line(
+ &mut self,
+ line_index: usize,
+ line_range: Range<usize>,
+ line_number: usize,
+ ) -> &mut Line<'diagnostic> {
+ self.lines.entry(line_index).or_insert_with(|| Line {
+ range: line_range,
+ number: line_number,
+ single_labels: vec![],
+ multi_labels: vec![],
+ // This has to be false by default so we know if it must be rendered by another condition already.
+ must_render: false,
+ })
+ }
+ }
+
+ struct Line<'diagnostic> {
+ number: usize,
+ range: std::ops::Range<usize>,
+ // TODO: How do we reuse these allocations?
+ single_labels: Vec<SingleLabel<'diagnostic>>,
+ multi_labels: Vec<(usize, LabelStyle, MultiLabel<'diagnostic>)>,
+ must_render: bool,
+ }
+
+ // TODO: Make this data structure external, to allow for allocation reuse
+ let mut labeled_files = Vec::<LabeledFile<'_, _>>::new();
+ // Keep track of the outer padding to use when rendering the
+ // snippets of source code.
+ let mut outer_padding = 0;
+
+ // Group labels by file
+ for label in &self.diagnostic.labels {
+ let start_line_index = files.line_index(label.file_id, label.range.start)?;
+ let start_line_number = files.line_number(label.file_id, start_line_index)?;
+ let start_line_range = files.line_range(label.file_id, start_line_index)?;
+ let end_line_index = files.line_index(label.file_id, label.range.end)?;
+ let end_line_number = files.line_number(label.file_id, end_line_index)?;
+ let end_line_range = files.line_range(label.file_id, end_line_index)?;
+
+ outer_padding = std::cmp::max(outer_padding, count_digits(start_line_number));
+ outer_padding = std::cmp::max(outer_padding, count_digits(end_line_number));
+
+ // NOTE: This could be made more efficient by using an associative
+ // data structure like a hashmap or B-tree, but we use a vector to
+ // preserve the order that unique files appear in the list of labels.
+ let labeled_file = match labeled_files
+ .iter_mut()
+ .find(|labeled_file| label.file_id == labeled_file.file_id)
+ {
+ Some(labeled_file) => {
+ // another diagnostic also referenced this file
+ if labeled_file.max_label_style > label.style
+ || (labeled_file.max_label_style == label.style
+ && labeled_file.start > label.range.start)
+ {
+ // this label has a higher style or has the same style but starts earlier
+ labeled_file.start = label.range.start;
+ labeled_file.location = files.location(label.file_id, label.range.start)?;
+ labeled_file.max_label_style = label.style;
+ }
+ labeled_file
+ }
+ None => {
+ // no other diagnostic referenced this file yet
+ labeled_files.push(LabeledFile {
+ file_id: label.file_id,
+ start: label.range.start,
+ name: files.name(label.file_id)?.to_string(),
+ location: files.location(label.file_id, label.range.start)?,
+ num_multi_labels: 0,
+ lines: BTreeMap::new(),
+ max_label_style: label.style,
+ });
+ // this unwrap should never fail because we just pushed an element
+ labeled_files
+ .last_mut()
+ .expect("just pushed an element that disappeared")
+ }
+ };
+
+ if start_line_index == end_line_index {
+ // Single line
+ //
+ // ```text
+ // 2 │ (+ test "")
+ // │ ^^ expected `Int` but found `String`
+ // ```
+ let label_start = label.range.start - start_line_range.start;
+ // Ensure that we print at least one caret, even when we
+ // have a zero-length source range.
+ let label_end =
+ usize::max(label.range.end - start_line_range.start, label_start + 1);
+
+ let line = labeled_file.get_or_insert_line(
+ start_line_index,
+ start_line_range,
+ start_line_number,
+ );
+
+ // Ensure that the single line labels are lexicographically
+ // sorted by the range of source code that they cover.
+ let index = match line.single_labels.binary_search_by(|(_, range, _)| {
+ // `Range<usize>` doesn't implement `Ord`, so convert to `(usize, usize)`
+ // to piggyback off its lexicographic comparison implementation.
+ (range.start, range.end).cmp(&(label_start, label_end))
+ }) {
+ // If the ranges are the same, order the labels in reverse
+ // to how they were originally specified in the diagnostic.
+ // This helps with printing in the renderer.
+ Ok(index) | Err(index) => index,
+ };
+
+ line.single_labels
+ .insert(index, (label.style, label_start..label_end, &label.message));
+
+ // If this line is not rendered, the SingleLabel is not visible.
+ line.must_render = true;
+ } else {
+ // Multiple lines
+ //
+ // ```text
+ // 4 │ fizz₁ num = case (mod num 5) (mod num 3) of
+ // │ ╭─────────────^
+ // 5 │ │ 0 0 => "FizzBuzz"
+ // 6 │ │ 0 _ => "Fizz"
+ // 7 │ │ _ 0 => "Buzz"
+ // 8 │ │ _ _ => num
+ // │ ╰──────────────^ `case` clauses have incompatible types
+ // ```
+
+ let label_index = labeled_file.num_multi_labels;
+ labeled_file.num_multi_labels += 1;
+
+ // First labeled line
+ let label_start = label.range.start - start_line_range.start;
+
+ let start_line = labeled_file.get_or_insert_line(
+ start_line_index,
+ start_line_range.clone(),
+ start_line_number,
+ );
+
+ start_line.multi_labels.push((
+ label_index,
+ label.style,
+ MultiLabel::Top(label_start),
+ ));
+
+ // The first line has to be rendered so the start of the label is visible.
+ start_line.must_render = true;
+
+ // Marked lines
+ //
+ // ```text
+ // 5 │ │ 0 0 => "FizzBuzz"
+ // 6 │ │ 0 _ => "Fizz"
+ // 7 │ │ _ 0 => "Buzz"
+ // ```
+ for line_index in (start_line_index + 1)..end_line_index {
+ let line_range = files.line_range(label.file_id, line_index)?;
+ let line_number = files.line_number(label.file_id, line_index)?;
+
+ outer_padding = std::cmp::max(outer_padding, count_digits(line_number));
+
+ let line = labeled_file.get_or_insert_line(line_index, line_range, line_number);
+
+ line.multi_labels
+ .push((label_index, label.style, MultiLabel::Left));
+
+ // The line should be rendered to match the configuration of how much context to show.
+ line.must_render |=
+ // Is this line part of the context after the start of the label?
+ line_index - start_line_index <= self.config.start_context_lines
+ ||
+ // Is this line part of the context before the end of the label?
+ end_line_index - line_index <= self.config.end_context_lines;
+ }
+
+ // Last labeled line
+ //
+ // ```text
+ // 8 │ │ _ _ => num
+ // │ ╰──────────────^ `case` clauses have incompatible types
+ // ```
+ let label_end = label.range.end - end_line_range.start;
+
+ let end_line = labeled_file.get_or_insert_line(
+ end_line_index,
+ end_line_range,
+ end_line_number,
+ );
+
+ end_line.multi_labels.push((
+ label_index,
+ label.style,
+ MultiLabel::Bottom(label_end, &label.message),
+ ));
+
+ // The last line has to be rendered so the end of the label is visible.
+ end_line.must_render = true;
+ }
+ }
+
+ // Header and message
+ //
+ // ```text
+ // error[E0001]: unexpected type in `+` application
+ // ```
+ renderer.render_header(
+ None,
+ self.diagnostic.severity,
+ self.diagnostic.code.as_deref(),
+ self.diagnostic.message.as_str(),
+ )?;
+
+ // Source snippets
+ //
+ // ```text
+ // ┌─ test:2:9
+ // │
+ // 2 │ (+ test "")
+ // │ ^^ expected `Int` but found `String`
+ // │
+ // ```
+ let mut labeled_files = labeled_files.into_iter().peekable();
+ while let Some(labeled_file) = labeled_files.next() {
+ let source = files.source(labeled_file.file_id)?;
+ let source = source.as_ref();
+
+ // Top left border and locus.
+ //
+ // ```text
+ // ┌─ test:2:9
+ // ```
+ if !labeled_file.lines.is_empty() {
+ renderer.render_snippet_start(
+ outer_padding,
+ &Locus {
+ name: labeled_file.name,
+ location: labeled_file.location,
+ },
+ )?;
+ renderer.render_snippet_empty(
+ outer_padding,
+ self.diagnostic.severity,
+ labeled_file.num_multi_labels,
+ &[],
+ )?;
+ }
+
+ let mut lines = labeled_file
+ .lines
+ .iter()
+ .filter(|(_, line)| line.must_render)
+ .peekable();
+
+ while let Some((line_index, line)) = lines.next() {
+ renderer.render_snippet_source(
+ outer_padding,
+ line.number,
+ &source[line.range.clone()],
+ self.diagnostic.severity,
+ &line.single_labels,
+ labeled_file.num_multi_labels,
+ &line.multi_labels,
+ )?;
+
+ // Check to see if we need to render any intermediate stuff
+ // before rendering the next line.
+ if let Some((next_line_index, _)) = lines.peek() {
+ match next_line_index.checked_sub(*line_index) {
+ // Consecutive lines
+ Some(1) => {}
+ // One line between the current line and the next line
+ Some(2) => {
+ // Write a source line
+ let file_id = labeled_file.file_id;
+
+ // This line was not intended to be rendered initially.
+ // To render the line right, we have to get back the original labels.
+ let labels = labeled_file
+ .lines
+ .get(&(line_index + 1))
+ .map_or(&[][..], |line| &line.multi_labels[..]);
+
+ renderer.render_snippet_source(
+ outer_padding,
+ files.line_number(file_id, line_index + 1)?,
+ &source[files.line_range(file_id, line_index + 1)?],
+ self.diagnostic.severity,
+ &[],
+ labeled_file.num_multi_labels,
+ labels,
+ )?;
+ }
+ // More than one line between the current line and the next line.
+ Some(_) | None => {
+ // Source break
+ //
+ // ```text
+ // ·
+ // ```
+ renderer.render_snippet_break(
+ outer_padding,
+ self.diagnostic.severity,
+ labeled_file.num_multi_labels,
+ &line.multi_labels,
+ )?;
+ }
+ }
+ }
+ }
+
+ // Check to see if we should render a trailing border after the
+ // final line of the snippet.
+ if labeled_files.peek().is_none() && self.diagnostic.notes.is_empty() {
+ // We don't render a border if we are at the final newline
+ // without trailing notes, because it would end up looking too
+ // spaced-out in combination with the final new line.
+ } else {
+ // Render the trailing snippet border.
+ renderer.render_snippet_empty(
+ outer_padding,
+ self.diagnostic.severity,
+ labeled_file.num_multi_labels,
+ &[],
+ )?;
+ }
+ }
+
+ // Additional notes
+ //
+ // ```text
+ // = expected type `Int`
+ // found type `String`
+ // ```
+ for note in &self.diagnostic.notes {
+ renderer.render_snippet_note(outer_padding, note)?;
+ }
+ renderer.render_empty()
+ }
+}
+
+/// Output a short diagnostic, with a line number, severity, and message.
+pub struct ShortDiagnostic<'diagnostic, FileId> {
+ diagnostic: &'diagnostic Diagnostic<FileId>,
+ show_notes: bool,
+}
+
+impl<'diagnostic, FileId> ShortDiagnostic<'diagnostic, FileId>
+where
+ FileId: Copy + PartialEq,
+{
+ pub fn new(
+ diagnostic: &'diagnostic Diagnostic<FileId>,
+ show_notes: bool,
+ ) -> ShortDiagnostic<'diagnostic, FileId> {
+ ShortDiagnostic {
+ diagnostic,
+ show_notes,
+ }
+ }
+
+ pub fn render<'files>(
+ &self,
+ files: &'files impl Files<'files, FileId = FileId>,
+ renderer: &mut Renderer<'_, '_>,
+ ) -> Result<(), Error>
+ where
+ FileId: 'files,
+ {
+ // Located headers
+ //
+ // ```text
+ // test:2:9: error[E0001]: unexpected type in `+` application
+ // ```
+ let mut primary_labels_encountered = 0;
+ let labels = self.diagnostic.labels.iter();
+ for label in labels.filter(|label| label.style == LabelStyle::Primary) {
+ primary_labels_encountered += 1;
+
+ renderer.render_header(
+ Some(&Locus {
+ name: files.name(label.file_id)?.to_string(),
+ location: files.location(label.file_id, label.range.start)?,
+ }),
+ self.diagnostic.severity,
+ self.diagnostic.code.as_deref(),
+ self.diagnostic.message.as_str(),
+ )?;
+ }
+
+ // Fallback to printing a non-located header if no primary labels were encountered
+ //
+ // ```text
+ // error[E0002]: Bad config found
+ // ```
+ if primary_labels_encountered == 0 {
+ renderer.render_header(
+ None,
+ self.diagnostic.severity,
+ self.diagnostic.code.as_deref(),
+ self.diagnostic.message.as_str(),
+ )?;
+ }
+
+ if self.show_notes {
+ // Additional notes
+ //
+ // ```text
+ // = expected type `Int`
+ // found type `String`
+ // ```
+ for note in &self.diagnostic.notes {
+ renderer.render_snippet_note(0, note)?;
+ }
+ }
+
+ Ok(())
+ }
+}