| // Code for annotating snippets. | |
| use rustc_macros::{Decodable, Encodable}; | |
| use crate::{Level, Loc}; | |
| #[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] | |
| pub(crate) struct Line { | |
| pub line_index: usize, | |
| pub annotations: Vec<Annotation>, | |
| } | |
| #[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Default)] | |
| pub(crate) struct AnnotationColumn { | |
| /// the (0-indexed) column for *display* purposes, counted in characters, not utf-8 bytes | |
| pub display: usize, | |
| /// the (0-indexed) column in the file, counted in characters, not utf-8 bytes. | |
| /// | |
| /// this may be different from `self.display`, | |
| /// e.g. if the file contains hard tabs, because we convert tabs to spaces for error messages. | |
| /// | |
| /// for example: | |
| /// ```text | |
| /// (hard tab)hello | |
| /// ^ this is display column 4, but file column 1 | |
| /// ``` | |
| /// | |
| /// we want to keep around the correct file offset so that column numbers in error messages | |
| /// are correct. (motivated by <https://github.com/rust-lang/rust/issues/109537>) | |
| pub file: usize, | |
| } | |
| impl AnnotationColumn { | |
| pub(crate) fn from_loc(loc: &Loc) -> AnnotationColumn { | |
| AnnotationColumn { display: loc.col_display, file: loc.col.0 } | |
| } | |
| } | |
| #[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] | |
| pub(crate) struct MultilineAnnotation { | |
| pub depth: usize, | |
| pub line_start: usize, | |
| pub line_end: usize, | |
| pub start_col: AnnotationColumn, | |
| pub end_col: AnnotationColumn, | |
| pub is_primary: bool, | |
| pub label: Option<String>, | |
| pub overlaps_exactly: bool, | |
| } | |
| impl MultilineAnnotation { | |
| pub(crate) fn increase_depth(&mut self) { | |
| self.depth += 1; | |
| } | |
| /// Compare two `MultilineAnnotation`s considering only the `Span` they cover. | |
| pub(crate) fn same_span(&self, other: &MultilineAnnotation) -> bool { | |
| self.line_start == other.line_start | |
| && self.line_end == other.line_end | |
| && self.start_col == other.start_col | |
| && self.end_col == other.end_col | |
| } | |
| pub(crate) fn as_start(&self) -> Annotation { | |
| Annotation { | |
| start_col: self.start_col, | |
| end_col: AnnotationColumn { | |
| // these might not correspond to the same place anymore, | |
| // but that's okay for our purposes | |
| display: self.start_col.display + 1, | |
| file: self.start_col.file + 1, | |
| }, | |
| is_primary: self.is_primary, | |
| label: None, | |
| annotation_type: AnnotationType::MultilineStart(self.depth), | |
| } | |
| } | |
| pub(crate) fn as_end(&self) -> Annotation { | |
| Annotation { | |
| start_col: AnnotationColumn { | |
| // these might not correspond to the same place anymore, | |
| // but that's okay for our purposes | |
| display: self.end_col.display.saturating_sub(1), | |
| file: self.end_col.file.saturating_sub(1), | |
| }, | |
| end_col: self.end_col, | |
| is_primary: self.is_primary, | |
| label: self.label.clone(), | |
| annotation_type: AnnotationType::MultilineEnd(self.depth), | |
| } | |
| } | |
| pub(crate) fn as_line(&self) -> Annotation { | |
| Annotation { | |
| start_col: Default::default(), | |
| end_col: Default::default(), | |
| is_primary: self.is_primary, | |
| label: None, | |
| annotation_type: AnnotationType::MultilineLine(self.depth), | |
| } | |
| } | |
| } | |
| #[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] | |
| pub(crate) enum AnnotationType { | |
| /// Annotation under a single line of code | |
| Singleline, | |
| // The Multiline type above is replaced with the following three in order | |
| // to reuse the current label drawing code. | |
| // | |
| // Each of these corresponds to one part of the following diagram: | |
| // | |
| // x | foo(1 + bar(x, | |
| // | _________^ < MultilineStart | |
| // x | | y), < MultilineLine | |
| // | |______________^ label < MultilineEnd | |
| // x | z); | |
| /// Annotation marking the first character of a fully shown multiline span | |
| MultilineStart(usize), | |
| /// Annotation marking the last character of a fully shown multiline span | |
| MultilineEnd(usize), | |
| /// Line at the left enclosing the lines of a fully shown multiline span | |
| // Just a placeholder for the drawing algorithm, to know that it shouldn't skip the first 4 | |
| // and last 2 lines of code. The actual line is drawn in `emit_message_default` and not in | |
| // `draw_multiline_line`. | |
| MultilineLine(usize), | |
| } | |
| #[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] | |
| pub(crate) struct Annotation { | |
| /// Start column. | |
| /// Note that it is important that this field goes | |
| /// first, so that when we sort, we sort orderings by start | |
| /// column. | |
| pub start_col: AnnotationColumn, | |
| /// End column within the line (exclusive) | |
| pub end_col: AnnotationColumn, | |
| /// Is this annotation derived from primary span | |
| pub is_primary: bool, | |
| /// Optional label to display adjacent to the annotation. | |
| pub label: Option<String>, | |
| /// Is this a single line, multiline or multiline span minimized down to a | |
| /// smaller span. | |
| pub annotation_type: AnnotationType, | |
| } | |
| impl Annotation { | |
| /// Whether this annotation is a vertical line placeholder. | |
| pub(crate) fn is_line(&self) -> bool { | |
| matches!(self.annotation_type, AnnotationType::MultilineLine(_)) | |
| } | |
| /// Length of this annotation as displayed in the stderr output | |
| pub(crate) fn len(&self) -> usize { | |
| // Account for usize underflows | |
| self.end_col.display.abs_diff(self.start_col.display) | |
| } | |
| pub(crate) fn has_label(&self) -> bool { | |
| if let Some(ref label) = self.label { | |
| // Consider labels with no text as effectively not being there | |
| // to avoid weird output with unnecessary vertical lines, like: | |
| // | |
| // X | fn foo(x: u32) { | |
| // | -------^------ | |
| // | | | | |
| // | | | |
| // | | |
| // | |
| // Note that this would be the complete output users would see. | |
| !label.is_empty() | |
| } else { | |
| false | |
| } | |
| } | |
| pub(crate) fn takes_space(&self) -> bool { | |
| // Multiline annotations always have to keep vertical space. | |
| matches!( | |
| self.annotation_type, | |
| AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_) | |
| ) | |
| } | |
| } | |
| #[derive(Debug)] | |
| pub(crate) struct StyledString { | |
| pub text: String, | |
| pub style: Style, | |
| } | |
| #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)] | |
| pub enum Style { | |
| MainHeaderMsg, | |
| HeaderMsg, | |
| LineAndColumn, | |
| LineNumber, | |
| Quotation, | |
| UnderlinePrimary, | |
| UnderlineSecondary, | |
| LabelPrimary, | |
| LabelSecondary, | |
| NoStyle, | |
| Level(Level), | |
| Highlight, | |
| Addition, | |
| Removal, | |
| } |