]> git.proxmox.com Git - rustc.git/blame - vendor/annotate-snippets-0.6.1/src/formatter/mod.rs
Update upstream source from tag 'upstream/1.52.1+dfsg1'
[rustc.git] / vendor / annotate-snippets-0.6.1 / src / formatter / mod.rs
CommitLineData
f20569fa
XL
1//! DisplayListFormatter is a module handling the formatting of a
2//! `DisplayList` into a formatted string.
3//!
4//! Besides formatting into a string it also uses a `style::Stylesheet` to
5//! provide additional styling like colors and emphasis to the text.
6
7pub mod style;
8
9use self::style::{Style, StyleClass, Stylesheet};
10use crate::display_list::*;
11use std::cmp;
12
13use crate::stylesheets::no_color::NoColorStylesheet;
14#[cfg(feature = "ansi_term")]
15use crate::stylesheets::color::AnsiTermStylesheet;
16
17fn repeat_char(c: char, n: usize) -> String {
18 let mut s = String::with_capacity(c.len_utf8());
19 s.push(c);
20 s.repeat(n)
21}
22
23/// DisplayListFormatter' constructor accepts two arguments:
24///
25/// * `color` allows the formatter to optionally apply colors and emphasis
26/// using the `ansi_term` crate.
27/// * `anonymized_line_numbers` will replace line numbers in the left column with the text `LL`.
28///
29/// Example:
30///
31/// ```
32/// use annotate_snippets::formatter::DisplayListFormatter;
33/// use annotate_snippets::display_list::{DisplayList, DisplayLine, DisplaySourceLine};
34///
35/// let dlf = DisplayListFormatter::new(false, false); // Don't use colors, Don't anonymize line numbers
36///
37/// let dl = DisplayList {
38/// body: vec![
39/// DisplayLine::Source {
40/// lineno: Some(192),
41/// inline_marks: vec![],
42/// line: DisplaySourceLine::Content {
43/// text: "Example line of text".into(),
44/// range: (0, 21)
45/// }
46/// }
47/// ]
48/// };
49/// assert_eq!(dlf.format(&dl), "192 | Example line of text");
50/// ```
51pub struct DisplayListFormatter {
52 stylesheet: Box<dyn Stylesheet>,
53 anonymized_line_numbers: bool,
54}
55
56impl DisplayListFormatter {
57 const ANONYMIZED_LINE_NUM: &'static str = "LL";
58
59 /// Constructor for the struct.
60 ///
61 /// The argument `color` selects the stylesheet depending on the user preferences and
62 /// `ansi_term` crate availability.
63 ///
64 /// The argument `anonymized_line_numbers` will replace line numbers in the left column with
65 /// the text `LL`. This can be useful to enable when running UI tests, such as in the Rust
66 /// test suite.
67 pub fn new(color: bool, anonymized_line_numbers: bool) -> Self {
68 if color {
69 Self {
70 #[cfg(feature = "ansi_term")]
71 stylesheet: Box::new(AnsiTermStylesheet {}),
72 #[cfg(not(feature = "ansi_term"))]
73 stylesheet: Box::new(NoColorStylesheet {}),
74 anonymized_line_numbers,
75 }
76 } else {
77 Self {
78 stylesheet: Box::new(NoColorStylesheet {}),
79 anonymized_line_numbers,
80 }
81 }
82 }
83
84 /// Formats a `DisplayList` into a String.
85 pub fn format(&self, dl: &DisplayList) -> String {
86 let lineno_width = dl.body.iter().fold(0, |max, line| match line {
87 DisplayLine::Source {
88 lineno: Some(lineno),
89 ..
90 } => {
91 if self.anonymized_line_numbers {
92 Self::ANONYMIZED_LINE_NUM.len()
93 } else {
94 cmp::max(lineno.to_string().len(), max)
95 }
96 },
97 _ => max,
98 });
99 let inline_marks_width = dl.body.iter().fold(0, |max, line| match line {
100 DisplayLine::Source { inline_marks, .. } => cmp::max(inline_marks.len(), max),
101 _ => max,
102 });
103
104 dl.body
105 .iter()
106 .map(|line| self.format_line(line, lineno_width, inline_marks_width))
107 .collect::<Vec<String>>()
108 .join("\n")
109 }
110
111 fn format_annotation_type(&self, annotation_type: &DisplayAnnotationType) -> &'static str {
112 match annotation_type {
113 DisplayAnnotationType::Error => "error",
114 DisplayAnnotationType::Warning => "warning",
115 DisplayAnnotationType::Info => "info",
116 DisplayAnnotationType::Note => "note",
117 DisplayAnnotationType::Help => "help",
118 DisplayAnnotationType::None => "",
119 }
120 }
121
122 fn get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> Box<dyn Style> {
123 self.stylesheet.get_style(match annotation_type {
124 DisplayAnnotationType::Error => StyleClass::Error,
125 DisplayAnnotationType::Warning => StyleClass::Warning,
126 DisplayAnnotationType::Info => StyleClass::Info,
127 DisplayAnnotationType::Note => StyleClass::Note,
128 DisplayAnnotationType::Help => StyleClass::Help,
129 DisplayAnnotationType::None => StyleClass::None,
130 })
131 }
132
133 fn format_label(&self, label: &[DisplayTextFragment]) -> String {
134 let emphasis_style = self.stylesheet.get_style(StyleClass::Emphasis);
135 label
136 .iter()
137 .map(|fragment| match fragment.style {
138 DisplayTextStyle::Regular => fragment.content.clone(),
139 DisplayTextStyle::Emphasis => emphasis_style.paint(&fragment.content),
140 })
141 .collect::<Vec<String>>()
142 .join("")
143 }
144
145 fn format_annotation(
146 &self,
147 annotation: &Annotation,
148 continuation: bool,
149 in_source: bool,
150 ) -> String {
151 let color = self.get_annotation_style(&annotation.annotation_type);
152 let formatted_type = if let Some(ref id) = annotation.id {
153 format!(
154 "{}[{}]",
155 self.format_annotation_type(&annotation.annotation_type),
156 id
157 )
158 } else {
159 self.format_annotation_type(&annotation.annotation_type)
160 .to_string()
161 };
162 let label = self.format_label(&annotation.label);
163
164 let label_part = if label.is_empty() {
165 "".to_string()
166 } else if in_source {
167 color.paint(&format!(": {}", self.format_label(&annotation.label)))
168 } else {
169 format!(": {}", self.format_label(&annotation.label))
170 };
171 if continuation {
172 let indent = formatted_type.len() + 2;
173 return format!("{}{}", repeat_char(' ', indent), label);
174 }
175 if !formatted_type.is_empty() {
176 format!("{}{}", color.paint(&formatted_type), label_part)
177 } else {
178 label
179 }
180 }
181
182 fn format_source_line(&self, line: &DisplaySourceLine) -> Option<String> {
183 match line {
184 DisplaySourceLine::Empty => None,
185 DisplaySourceLine::Content { text, .. } => Some(format!(" {}", text)),
186 DisplaySourceLine::Annotation {
187 range,
188 annotation,
189 annotation_type,
190 annotation_part,
191 } => {
192 let indent_char = match annotation_part {
193 DisplayAnnotationPart::Standalone => ' ',
194 DisplayAnnotationPart::LabelContinuation => ' ',
195 DisplayAnnotationPart::Consequitive => ' ',
196 DisplayAnnotationPart::MultilineStart => '_',
197 DisplayAnnotationPart::MultilineEnd => '_',
198 };
199 let mark = match annotation_type {
200 DisplayAnnotationType::Error => '^',
201 DisplayAnnotationType::Warning => '-',
202 DisplayAnnotationType::Info => '-',
203 DisplayAnnotationType::Note => '-',
204 DisplayAnnotationType::Help => '-',
205 DisplayAnnotationType::None => ' ',
206 };
207 let color = self.get_annotation_style(annotation_type);
208 let indent_length = match annotation_part {
209 DisplayAnnotationPart::LabelContinuation => range.1,
210 DisplayAnnotationPart::Consequitive => range.1,
211 _ => range.0,
212 };
213 let indent = color.paint(&repeat_char(indent_char, indent_length + 1));
214 let marks = color.paint(&repeat_char(mark, range.1 - indent_length));
215 let annotation = self.format_annotation(
216 annotation,
217 annotation_part == &DisplayAnnotationPart::LabelContinuation,
218 true,
219 );
220 if annotation.is_empty() {
221 return Some(format!("{}{}", indent, marks));
222 }
223 Some(format!("{}{} {}", indent, marks, color.paint(&annotation)))
224 }
225 }
226 }
227
228 fn format_lineno(&self, lineno: Option<usize>, lineno_width: usize) -> String {
229 match lineno {
230 Some(n) => format!("{:>width$}", n, width = lineno_width),
231 None => repeat_char(' ', lineno_width),
232 }
233 }
234
235 fn format_raw_line(&self, line: &DisplayRawLine, lineno_width: usize) -> String {
236 match line {
237 DisplayRawLine::Origin {
238 path,
239 pos,
240 header_type,
241 } => {
242 let header_sigil = match header_type {
243 DisplayHeaderType::Initial => "-->",
244 DisplayHeaderType::Continuation => ":::",
245 };
246 let lineno_color = self.stylesheet.get_style(StyleClass::LineNo);
247
248 if let Some((col, row)) = pos {
249 format!(
250 "{}{} {}:{}:{}",
251 repeat_char(' ', lineno_width),
252 lineno_color.paint(header_sigil),
253 path,
254 col,
255 row
256 )
257 } else {
258 format!(
259 "{}{} {}",
260 repeat_char(' ', lineno_width),
261 lineno_color.paint(header_sigil),
262 path
263 )
264 }
265 }
266 DisplayRawLine::Annotation {
267 annotation,
268 source_aligned,
269 continuation,
270 } => {
271 if *source_aligned {
272 if *continuation {
273 format!(
274 "{}{}",
275 repeat_char(' ', lineno_width + 3),
276 self.format_annotation(annotation, *continuation, false)
277 )
278 } else {
279 let lineno_color = self.stylesheet.get_style(StyleClass::LineNo);
280 format!(
281 "{} {} {}",
282 repeat_char(' ', lineno_width),
283 lineno_color.paint("="),
284 self.format_annotation(annotation, *continuation, false)
285 )
286 }
287 } else {
288 self.format_annotation(annotation, *continuation, false)
289 }
290 }
291 }
292 }
293
294 fn format_line(
295 &self,
296 dl: &DisplayLine,
297 lineno_width: usize,
298 inline_marks_width: usize,
299 ) -> String {
300 match dl {
301 DisplayLine::Source {
302 lineno,
303 inline_marks,
304 line,
305 } => {
306 let lineno = if self.anonymized_line_numbers && lineno.is_some() {
307 Self::ANONYMIZED_LINE_NUM.to_string()
308 } else {
309 self.format_lineno(*lineno, lineno_width)
310 };
311 let marks = self.format_inline_marks(inline_marks, inline_marks_width);
312 let lf = self.format_source_line(line);
313 let lineno_color = self.stylesheet.get_style(StyleClass::LineNo);
314
315 let mut prefix = lineno_color.paint(&format!("{} |", lineno));
316
317 match lf {
318 Some(lf) => {
319 if !marks.is_empty() {
320 prefix.push_str(&format!(" {}", marks));
321 }
322 format!("{}{}", prefix, lf)
323 }
324 None => {
325 if !marks.trim().is_empty() {
326 prefix.push_str(&format!(" {}", marks));
327 }
328 prefix
329 }
330 }
331 }
332 DisplayLine::Fold { inline_marks } => {
333 let marks = self.format_inline_marks(inline_marks, inline_marks_width);
334 let indent = lineno_width;
335 if marks.trim().is_empty() {
336 String::from("...")
337 } else {
338 format!("...{}{}", repeat_char(' ', indent), marks)
339 }
340 }
341 DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width),
342 }
343 }
344
345 fn format_inline_marks(
346 &self,
347 inline_marks: &[DisplayMark],
348 inline_marks_width: usize,
349 ) -> String {
350 format!(
351 "{}{}",
352 " ".repeat(inline_marks_width - inline_marks.len()),
353 inline_marks
354 .iter()
355 .map(|mark| {
356 let sigil = match mark.mark_type {
357 DisplayMarkType::AnnotationThrough => "|",
358 DisplayMarkType::AnnotationStart => "/",
359 };
360 let color = self.get_annotation_style(&mark.annotation_type);
361 color.paint(sigil)
362 })
363 .collect::<Vec<String>>()
364 .join(""),
365 )
366 }
367}