rustc_errors/
snippet.rs

1// Code for annotating snippets.
2
3use rustc_macros::{Decodable, Encodable};
4
5use crate::{Level, Loc};
6
7#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
8pub(crate) struct Line {
9    pub line_index: usize,
10    pub annotations: Vec<Annotation>,
11}
12
13#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Default)]
14pub(crate) struct AnnotationColumn {
15    /// the (0-indexed) column for *display* purposes, counted in characters, not utf-8 bytes
16    pub display: usize,
17    /// the (0-indexed) column in the file, counted in characters, not utf-8 bytes.
18    ///
19    /// this may be different from `self.display`,
20    /// e.g. if the file contains hard tabs, because we convert tabs to spaces for error messages.
21    ///
22    /// for example:
23    /// ```text
24    /// (hard tab)hello
25    ///           ^ this is display column 4, but file column 1
26    /// ```
27    ///
28    /// we want to keep around the correct file offset so that column numbers in error messages
29    /// are correct. (motivated by <https://github.com/rust-lang/rust/issues/109537>)
30    pub file: usize,
31}
32
33impl AnnotationColumn {
34    pub(crate) fn from_loc(loc: &Loc) -> AnnotationColumn {
35        AnnotationColumn { display: loc.col_display, file: loc.col.0 }
36    }
37}
38
39#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
40pub(crate) struct MultilineAnnotation {
41    pub depth: usize,
42    pub line_start: usize,
43    pub line_end: usize,
44    pub start_col: AnnotationColumn,
45    pub end_col: AnnotationColumn,
46    pub is_primary: bool,
47    pub label: Option<String>,
48    pub overlaps_exactly: bool,
49}
50
51impl MultilineAnnotation {
52    pub(crate) fn increase_depth(&mut self) {
53        self.depth += 1;
54    }
55
56    /// Compare two `MultilineAnnotation`s considering only the `Span` they cover.
57    pub(crate) fn same_span(&self, other: &MultilineAnnotation) -> bool {
58        self.line_start == other.line_start
59            && self.line_end == other.line_end
60            && self.start_col == other.start_col
61            && self.end_col == other.end_col
62    }
63
64    pub(crate) fn as_start(&self) -> Annotation {
65        Annotation {
66            start_col: self.start_col,
67            end_col: AnnotationColumn {
68                // these might not correspond to the same place anymore,
69                // but that's okay for our purposes
70                display: self.start_col.display + 1,
71                file: self.start_col.file + 1,
72            },
73            is_primary: self.is_primary,
74            label: None,
75            annotation_type: AnnotationType::MultilineStart(self.depth),
76        }
77    }
78
79    pub(crate) fn as_end(&self) -> Annotation {
80        Annotation {
81            start_col: AnnotationColumn {
82                // these might not correspond to the same place anymore,
83                // but that's okay for our purposes
84                display: self.end_col.display.saturating_sub(1),
85                file: self.end_col.file.saturating_sub(1),
86            },
87            end_col: self.end_col,
88            is_primary: self.is_primary,
89            label: self.label.clone(),
90            annotation_type: AnnotationType::MultilineEnd(self.depth),
91        }
92    }
93
94    pub(crate) fn as_line(&self) -> Annotation {
95        Annotation {
96            start_col: Default::default(),
97            end_col: Default::default(),
98            is_primary: self.is_primary,
99            label: None,
100            annotation_type: AnnotationType::MultilineLine(self.depth),
101        }
102    }
103}
104
105#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
106pub(crate) enum AnnotationType {
107    /// Annotation under a single line of code
108    Singleline,
109
110    // The Multiline type above is replaced with the following three in order
111    // to reuse the current label drawing code.
112    //
113    // Each of these corresponds to one part of the following diagram:
114    //
115    //     x |   foo(1 + bar(x,
116    //       |  _________^              < MultilineStart
117    //     x | |             y),        < MultilineLine
118    //       | |______________^ label   < MultilineEnd
119    //     x |       z);
120    /// Annotation marking the first character of a fully shown multiline span
121    MultilineStart(usize),
122    /// Annotation marking the last character of a fully shown multiline span
123    MultilineEnd(usize),
124    /// Line at the left enclosing the lines of a fully shown multiline span
125    // Just a placeholder for the drawing algorithm, to know that it shouldn't skip the first 4
126    // and last 2 lines of code. The actual line is drawn in `emit_message_default` and not in
127    // `draw_multiline_line`.
128    MultilineLine(usize),
129}
130
131#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
132pub(crate) struct Annotation {
133    /// Start column.
134    /// Note that it is important that this field goes
135    /// first, so that when we sort, we sort orderings by start
136    /// column.
137    pub start_col: AnnotationColumn,
138
139    /// End column within the line (exclusive)
140    pub end_col: AnnotationColumn,
141
142    /// Is this annotation derived from primary span
143    pub is_primary: bool,
144
145    /// Optional label to display adjacent to the annotation.
146    pub label: Option<String>,
147
148    /// Is this a single line, multiline or multiline span minimized down to a
149    /// smaller span.
150    pub annotation_type: AnnotationType,
151}
152
153impl Annotation {
154    /// Whether this annotation is a vertical line placeholder.
155    pub(crate) fn is_line(&self) -> bool {
156        matches!(self.annotation_type, AnnotationType::MultilineLine(_))
157    }
158
159    /// Length of this annotation as displayed in the stderr output
160    pub(crate) fn len(&self) -> usize {
161        // Account for usize underflows
162        if self.end_col.display > self.start_col.display {
163            self.end_col.display - self.start_col.display
164        } else {
165            self.start_col.display - self.end_col.display
166        }
167    }
168
169    pub(crate) fn has_label(&self) -> bool {
170        if let Some(ref label) = self.label {
171            // Consider labels with no text as effectively not being there
172            // to avoid weird output with unnecessary vertical lines, like:
173            //
174            //     X | fn foo(x: u32) {
175            //       | -------^------
176            //       | |      |
177            //       | |
178            //       |
179            //
180            // Note that this would be the complete output users would see.
181            !label.is_empty()
182        } else {
183            false
184        }
185    }
186
187    pub(crate) fn takes_space(&self) -> bool {
188        // Multiline annotations always have to keep vertical space.
189        matches!(
190            self.annotation_type,
191            AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_)
192        )
193    }
194}
195
196#[derive(Debug)]
197pub(crate) struct StyledString {
198    pub text: String,
199    pub style: Style,
200}
201
202#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)]
203pub enum Style {
204    MainHeaderMsg,
205    HeaderMsg,
206    LineAndColumn,
207    LineNumber,
208    Quotation,
209    UnderlinePrimary,
210    UnderlineSecondary,
211    LabelPrimary,
212    LabelSecondary,
213    NoStyle,
214    Level(Level),
215    Highlight,
216    Addition,
217    Removal,
218}