]>
Commit | Line | Data |
---|---|---|
9cc50fc6 | 1 | // Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT |
223e47cc LB |
2 | // file at the top-level directory of this distribution and at |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
1a4d82fc JJ |
11 | use self::Destination::*; |
12 | ||
abe05a73 | 13 | use syntax_pos::{DUMMY_SP, FileMap, Span, MultiSpan}; |
223e47cc | 14 | |
abe05a73 | 15 | use {Level, CodeSuggestion, DiagnosticBuilder, SubDiagnostic, CodeMapper, DiagnosticId}; |
476ff2be | 16 | use snippet::{Annotation, AnnotationType, Line, MultilineAnnotation, StyledString, Style}; |
5bcae85e | 17 | use styled_buffer::StyledBuffer; |
9cc50fc6 | 18 | |
041b39d2 | 19 | use std::borrow::Cow; |
c34b1796 AL |
20 | use std::io::prelude::*; |
21 | use std::io; | |
9cc50fc6 | 22 | use std::rc::Rc; |
92a42be0 | 23 | use term; |
cc61c64b XL |
24 | use std::collections::HashMap; |
25 | use std::cmp::min; | |
1a4d82fc | 26 | |
5bcae85e | 27 | /// Emitter trait for emitting errors. |
9cc50fc6 | 28 | pub trait Emitter { |
9cc50fc6 | 29 | /// Emit a structured diagnostic. |
5bcae85e | 30 | fn emit(&mut self, db: &DiagnosticBuilder); |
a7813a04 XL |
31 | } |
32 | ||
5bcae85e SL |
33 | impl Emitter for EmitterWriter { |
34 | fn emit(&mut self, db: &DiagnosticBuilder) { | |
9e0c209e SL |
35 | let mut primary_span = db.span.clone(); |
36 | let mut children = db.children.clone(); | |
abe05a73 | 37 | let mut suggestions: &[_] = &[]; |
7cac9316 XL |
38 | |
39 | if let Some((sugg, rest)) = db.suggestions.split_first() { | |
40 | if rest.is_empty() && | |
7cac9316 | 41 | // don't display multi-suggestions as labels |
abe05a73 XL |
42 | sugg.substitutions.len() == 1 && |
43 | // don't display multipart suggestions as labels | |
44 | sugg.substitutions[0].parts.len() == 1 && | |
7cac9316 XL |
45 | // don't display long messages as labels |
46 | sugg.msg.split_whitespace().count() < 10 && | |
47 | // don't display multiline suggestions as labels | |
abe05a73 XL |
48 | !sugg.substitutions[0].parts[0].snippet.contains('\n') { |
49 | let substitution = &sugg.substitutions[0].parts[0].snippet; | |
3b2f2976 | 50 | let msg = if substitution.len() == 0 || !sugg.show_code_when_inline { |
ea8adc8c | 51 | // This substitution is only removal or we explicitly don't want to show the |
3b2f2976 | 52 | // code inline, don't show it |
041b39d2 XL |
53 | format!("help: {}", sugg.msg) |
54 | } else { | |
55 | format!("help: {}: `{}`", sugg.msg, substitution) | |
56 | }; | |
abe05a73 | 57 | primary_span.push_span_label(sugg.substitutions[0].parts[0].span, msg); |
7cac9316 XL |
58 | } else { |
59 | // if there are multiple suggestions, print them all in full | |
60 | // to be consistent. We could try to figure out if we can | |
61 | // make one (or the first one) inline, but that would give | |
62 | // undue importance to a semi-random suggestion | |
abe05a73 | 63 | suggestions = &db.suggestions; |
7cac9316 XL |
64 | } |
65 | } | |
66 | ||
9e0c209e | 67 | self.fix_multispans_in_std_macros(&mut primary_span, &mut children); |
32a655c1 SL |
68 | self.emit_messages_default(&db.level, |
69 | &db.styled_message(), | |
70 | &db.code, | |
71 | &primary_span, | |
abe05a73 XL |
72 | &children, |
73 | &suggestions); | |
1a4d82fc | 74 | } |
223e47cc LB |
75 | } |
76 | ||
9cc50fc6 | 77 | /// maximum number of lines we will print for each error; arbitrary. |
7453a54e | 78 | pub const MAX_HIGHLIGHT_LINES: usize = 6; |
7cac9316 XL |
79 | /// maximum number of suggestions to be shown |
80 | /// | |
81 | /// Arbitrary, but taken from trait import suggestion limit | |
82 | pub const MAX_SUGGESTIONS: usize = 4; | |
7453a54e | 83 | |
9cc50fc6 | 84 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
1a4d82fc JJ |
85 | pub enum ColorConfig { |
86 | Auto, | |
87 | Always, | |
9cc50fc6 | 88 | Never, |
223e47cc LB |
89 | } |
90 | ||
9cc50fc6 SL |
91 | impl ColorConfig { |
92 | fn use_color(&self) -> bool { | |
93 | match *self { | |
94 | ColorConfig::Always => true, | |
c30ab7b3 SL |
95 | ColorConfig::Never => false, |
96 | ColorConfig::Auto => stderr_isatty(), | |
9cc50fc6 | 97 | } |
d9579d0f AL |
98 | } |
99 | } | |
100 | ||
9cc50fc6 SL |
101 | pub struct EmitterWriter { |
102 | dst: Destination, | |
5bcae85e | 103 | cm: Option<Rc<CodeMapper>>, |
abe05a73 | 104 | short_message: bool, |
a7813a04 | 105 | } |
223e47cc | 106 | |
5bcae85e SL |
107 | struct FileWithAnnotatedLines { |
108 | file: Rc<FileMap>, | |
109 | lines: Vec<Line>, | |
476ff2be | 110 | multiline_depth: usize, |
223e47cc LB |
111 | } |
112 | ||
1a4d82fc | 113 | impl EmitterWriter { |
abe05a73 XL |
114 | pub fn stderr(color_config: ColorConfig, |
115 | code_map: Option<Rc<CodeMapper>>, | |
116 | short_message: bool) | |
117 | -> EmitterWriter { | |
9cc50fc6 SL |
118 | if color_config.use_color() { |
119 | let dst = Destination::from_stderr(); | |
c30ab7b3 | 120 | EmitterWriter { |
3b2f2976 | 121 | dst, |
c30ab7b3 | 122 | cm: code_map, |
abe05a73 | 123 | short_message: short_message, |
c30ab7b3 | 124 | } |
1a4d82fc | 125 | } else { |
c30ab7b3 SL |
126 | EmitterWriter { |
127 | dst: Raw(Box::new(io::stderr())), | |
128 | cm: code_map, | |
abe05a73 | 129 | short_message: short_message, |
c30ab7b3 | 130 | } |
1a4d82fc JJ |
131 | } |
132 | } | |
133 | ||
abe05a73 XL |
134 | pub fn new(dst: Box<Write + Send>, |
135 | code_map: Option<Rc<CodeMapper>>, | |
136 | short_message: bool) | |
137 | -> EmitterWriter { | |
c30ab7b3 SL |
138 | EmitterWriter { |
139 | dst: Raw(dst), | |
140 | cm: code_map, | |
abe05a73 | 141 | short_message: short_message, |
c30ab7b3 | 142 | } |
a7813a04 XL |
143 | } |
144 | ||
041b39d2 | 145 | fn preprocess_annotations(&mut self, msp: &MultiSpan) -> Vec<FileWithAnnotatedLines> { |
5bcae85e | 146 | fn add_annotation_to_file(file_vec: &mut Vec<FileWithAnnotatedLines>, |
c30ab7b3 SL |
147 | file: Rc<FileMap>, |
148 | line_index: usize, | |
149 | ann: Annotation) { | |
5bcae85e SL |
150 | |
151 | for slot in file_vec.iter_mut() { | |
152 | // Look through each of our files for the one we're adding to | |
153 | if slot.file.name == file.name { | |
154 | // See if we already have a line for it | |
155 | for line_slot in &mut slot.lines { | |
156 | if line_slot.line_index == line_index { | |
157 | line_slot.annotations.push(ann); | |
158 | return; | |
159 | } | |
160 | } | |
161 | // We don't have a line yet, create one | |
162 | slot.lines.push(Line { | |
3b2f2976 | 163 | line_index, |
5bcae85e SL |
164 | annotations: vec![ann], |
165 | }); | |
166 | slot.lines.sort(); | |
167 | return; | |
a7813a04 XL |
168 | } |
169 | } | |
5bcae85e SL |
170 | // This is the first time we're seeing the file |
171 | file_vec.push(FileWithAnnotatedLines { | |
3b2f2976 | 172 | file, |
5bcae85e | 173 | lines: vec![Line { |
3b2f2976 | 174 | line_index, |
5bcae85e SL |
175 | annotations: vec![ann], |
176 | }], | |
476ff2be | 177 | multiline_depth: 0, |
5bcae85e | 178 | }); |
a7813a04 | 179 | } |
c1a9b12d | 180 | |
5bcae85e | 181 | let mut output = vec![]; |
476ff2be | 182 | let mut multiline_annotations = vec![]; |
5bcae85e SL |
183 | |
184 | if let Some(ref cm) = self.cm { | |
185 | for span_label in msp.span_labels() { | |
cc61c64b | 186 | if span_label.span == DUMMY_SP { |
5bcae85e | 187 | continue; |
a7813a04 | 188 | } |
041b39d2 | 189 | |
ea8adc8c XL |
190 | let lo = cm.lookup_char_pos(span_label.span.lo()); |
191 | let mut hi = cm.lookup_char_pos(span_label.span.hi()); | |
5bcae85e SL |
192 | |
193 | // Watch out for "empty spans". If we get a span like 6..6, we | |
194 | // want to just display a `^` at 6, so convert that to | |
195 | // 6..7. This is degenerate input, but it's best to degrade | |
196 | // gracefully -- and the parser likes to supply a span like | |
197 | // that for EOF, in particular. | |
abe05a73 XL |
198 | if lo.col_display == hi.col_display && lo.line == hi.line { |
199 | hi.col_display += 1; | |
a7813a04 | 200 | } |
5bcae85e | 201 | |
cc61c64b | 202 | let ann_type = if lo.line != hi.line { |
476ff2be SL |
203 | let ml = MultilineAnnotation { |
204 | depth: 1, | |
205 | line_start: lo.line, | |
206 | line_end: hi.line, | |
abe05a73 XL |
207 | start_col: lo.col_display, |
208 | end_col: hi.col_display, | |
476ff2be SL |
209 | is_primary: span_label.is_primary, |
210 | label: span_label.label.clone(), | |
211 | }; | |
cc61c64b XL |
212 | multiline_annotations.push((lo.file.clone(), ml.clone())); |
213 | AnnotationType::Multiline(ml) | |
214 | } else { | |
215 | AnnotationType::Singleline | |
216 | }; | |
217 | let ann = Annotation { | |
abe05a73 XL |
218 | start_col: lo.col_display, |
219 | end_col: hi.col_display, | |
cc61c64b XL |
220 | is_primary: span_label.is_primary, |
221 | label: span_label.label.clone(), | |
222 | annotation_type: ann_type, | |
476ff2be SL |
223 | }; |
224 | ||
225 | if !ann.is_multiline() { | |
226 | add_annotation_to_file(&mut output, | |
227 | lo.file, | |
228 | lo.line, | |
229 | ann); | |
230 | } | |
231 | } | |
232 | } | |
233 | ||
234 | // Find overlapping multiline annotations, put them at different depths | |
235 | multiline_annotations.sort_by(|a, b| { | |
236 | (a.1.line_start, a.1.line_end).cmp(&(b.1.line_start, b.1.line_end)) | |
237 | }); | |
238 | for item in multiline_annotations.clone() { | |
239 | let ann = item.1; | |
240 | for item in multiline_annotations.iter_mut() { | |
241 | let ref mut a = item.1; | |
242 | // Move all other multiline annotations overlapping with this one | |
243 | // one level to the right. | |
244 | if &ann != a && | |
245 | num_overlap(ann.line_start, ann.line_end, a.line_start, a.line_end, true) | |
246 | { | |
247 | a.increase_depth(); | |
248 | } else { | |
249 | break; | |
250 | } | |
a7813a04 XL |
251 | } |
252 | } | |
476ff2be SL |
253 | |
254 | let mut max_depth = 0; // max overlapping multiline spans | |
255 | for (file, ann) in multiline_annotations { | |
256 | if ann.depth > max_depth { | |
257 | max_depth = ann.depth; | |
258 | } | |
259 | add_annotation_to_file(&mut output, file.clone(), ann.line_start, ann.as_start()); | |
cc61c64b XL |
260 | let middle = min(ann.line_start + 4, ann.line_end); |
261 | for line in ann.line_start + 1..middle { | |
476ff2be SL |
262 | add_annotation_to_file(&mut output, file.clone(), line, ann.as_line()); |
263 | } | |
cc61c64b XL |
264 | if middle < ann.line_end - 1 { |
265 | for line in ann.line_end - 1..ann.line_end { | |
266 | add_annotation_to_file(&mut output, file.clone(), line, ann.as_line()); | |
267 | } | |
268 | } | |
476ff2be SL |
269 | add_annotation_to_file(&mut output, file, ann.line_end, ann.as_end()); |
270 | } | |
271 | for file_vec in output.iter_mut() { | |
272 | file_vec.multiline_depth = max_depth; | |
273 | } | |
5bcae85e SL |
274 | output |
275 | } | |
276 | ||
277 | fn render_source_line(&self, | |
278 | buffer: &mut StyledBuffer, | |
279 | file: Rc<FileMap>, | |
280 | line: &Line, | |
476ff2be | 281 | width_offset: usize, |
cc61c64b | 282 | code_offset: usize) -> Vec<(usize, Style)> { |
7cac9316 XL |
283 | let source_string = match file.get_line(line.line_index - 1) { |
284 | Some(s) => s, | |
285 | None => return Vec::new(), | |
286 | }; | |
5bcae85e SL |
287 | |
288 | let line_offset = buffer.num_lines(); | |
c1a9b12d | 289 | |
5bcae85e | 290 | // First create the source line we will highlight. |
476ff2be | 291 | buffer.puts(line_offset, code_offset, &source_string, Style::Quotation); |
5bcae85e SL |
292 | buffer.puts(line_offset, |
293 | 0, | |
294 | &(line.line_index.to_string()), | |
295 | Style::LineNumber); | |
296 | ||
297 | draw_col_separator(buffer, line_offset, width_offset - 2); | |
298 | ||
cc61c64b XL |
299 | // Special case when there's only one annotation involved, it is the start of a multiline |
300 | // span and there's no text at the beginning of the code line. Instead of doing the whole | |
301 | // graph: | |
302 | // | |
303 | // 2 | fn foo() { | |
304 | // | _^ | |
305 | // 3 | | | |
306 | // 4 | | } | |
307 | // | |_^ test | |
308 | // | |
309 | // we simplify the output to: | |
310 | // | |
311 | // 2 | / fn foo() { | |
312 | // 3 | | | |
313 | // 4 | | } | |
314 | // | |_^ test | |
315 | if line.annotations.len() == 1 { | |
316 | if let Some(ref ann) = line.annotations.get(0) { | |
317 | if let AnnotationType::MultilineStart(depth) = ann.annotation_type { | |
3b2f2976 XL |
318 | if source_string.chars() |
319 | .take(ann.start_col) | |
320 | .all(|c| c.is_whitespace()) { | |
cc61c64b XL |
321 | let style = if ann.is_primary { |
322 | Style::UnderlinePrimary | |
323 | } else { | |
324 | Style::UnderlineSecondary | |
325 | }; | |
326 | buffer.putc(line_offset, | |
327 | width_offset + depth - 1, | |
328 | '/', | |
329 | style); | |
330 | return vec![(depth, style)]; | |
331 | } | |
332 | } | |
333 | } | |
334 | } | |
335 | ||
5bcae85e SL |
336 | // We want to display like this: |
337 | // | |
338 | // vec.push(vec.pop().unwrap()); | |
476ff2be | 339 | // --- ^^^ - previous borrow ends here |
5bcae85e SL |
340 | // | | |
341 | // | error occurs here | |
342 | // previous borrow of `vec` occurs here | |
343 | // | |
344 | // But there are some weird edge cases to be aware of: | |
345 | // | |
346 | // vec.push(vec.pop().unwrap()); | |
347 | // -------- - previous borrow ends here | |
348 | // || | |
349 | // |this makes no sense | |
350 | // previous borrow of `vec` occurs here | |
351 | // | |
352 | // For this reason, we group the lines into "highlight lines" | |
cc61c64b | 353 | // and "annotations lines", where the highlight lines have the `^`. |
5bcae85e SL |
354 | |
355 | // Sort the annotations by (start, end col) | |
3b2f2976 XL |
356 | // The labels are reversed, sort and then reversed again. |
357 | // Consider a list of annotations (A1, A2, C1, C2, B1, B2) where | |
358 | // the letter signifies the span. Here we are only sorting by the | |
359 | // span and hence, the order of the elements with the same span will | |
360 | // not change. On reversing the ordering (|a, b| but b.cmp(a)), you get | |
361 | // (C1, C2, B1, B2, A1, A2). All the elements with the same span are | |
362 | // still ordered first to last, but all the elements with different | |
363 | // spans are ordered by their spans in last to first order. Last to | |
364 | // first order is important, because the jiggly lines and | are on | |
365 | // the left, so the rightmost span needs to be rendered first, | |
366 | // otherwise the lines would end up needing to go over a message. | |
367 | ||
5bcae85e | 368 | let mut annotations = line.annotations.clone(); |
3b2f2976 | 369 | annotations.sort_by(|a,b| b.start_col.cmp(&a.start_col)); |
5bcae85e | 370 | |
476ff2be SL |
371 | // First, figure out where each label will be positioned. |
372 | // | |
373 | // In the case where you have the following annotations: | |
374 | // | |
375 | // vec.push(vec.pop().unwrap()); | |
376 | // -------- - previous borrow ends here [C] | |
377 | // || | |
378 | // |this makes no sense [B] | |
379 | // previous borrow of `vec` occurs here [A] | |
380 | // | |
381 | // `annotations_position` will hold [(2, A), (1, B), (0, C)]. | |
382 | // | |
383 | // We try, when possible, to stick the rightmost annotation at the end | |
384 | // of the highlight line: | |
5bcae85e SL |
385 | // |
386 | // vec.push(vec.pop().unwrap()); | |
387 | // --- --- - previous borrow ends here | |
388 | // | |
389 | // But sometimes that's not possible because one of the other | |
390 | // annotations overlaps it. For example, from the test | |
391 | // `span_overlap_label`, we have the following annotations | |
392 | // (written on distinct lines for clarity): | |
393 | // | |
394 | // fn foo(x: u32) { | |
395 | // -------------- | |
396 | // - | |
397 | // | |
398 | // In this case, we can't stick the rightmost-most label on | |
399 | // the highlight line, or we would get: | |
400 | // | |
401 | // fn foo(x: u32) { | |
402 | // -------- x_span | |
403 | // | | |
404 | // fn_span | |
405 | // | |
406 | // which is totally weird. Instead we want: | |
407 | // | |
408 | // fn foo(x: u32) { | |
409 | // -------------- | |
410 | // | | | |
411 | // | x_span | |
412 | // fn_span | |
413 | // | |
414 | // which is...less weird, at least. In fact, in general, if | |
415 | // the rightmost span overlaps with any other span, we should | |
416 | // use the "hang below" version, so we can at least make it | |
32a655c1 SL |
417 | // clear where the span *starts*. There's an exception for this |
418 | // logic, when the labels do not have a message: | |
419 | // | |
420 | // fn foo(x: u32) { | |
421 | // -------------- | |
422 | // | | |
423 | // x_span | |
424 | // | |
425 | // instead of: | |
426 | // | |
427 | // fn foo(x: u32) { | |
428 | // -------------- | |
429 | // | | | |
430 | // | x_span | |
431 | // <EMPTY LINE> | |
432 | // | |
476ff2be SL |
433 | let mut annotations_position = vec![]; |
434 | let mut line_len = 0; | |
435 | let mut p = 0; | |
8bb4bdeb XL |
436 | for (i, annotation) in annotations.iter().enumerate() { |
437 | for (j, next) in annotations.iter().enumerate() { | |
438 | if overlaps(next, annotation, 0) // This label overlaps with another one and both | |
cc61c64b XL |
439 | && annotation.has_label() // take space (they have text and are not |
440 | && j > i // multiline lines). | |
8bb4bdeb | 441 | && p == 0 // We're currently on the first line, move the label one line down |
32a655c1 SL |
442 | { |
443 | // This annotation needs a new line in the output. | |
476ff2be | 444 | p += 1; |
8bb4bdeb | 445 | break; |
a7813a04 | 446 | } |
c1a9b12d | 447 | } |
476ff2be | 448 | annotations_position.push((p, annotation)); |
8bb4bdeb XL |
449 | for (j, next) in annotations.iter().enumerate() { |
450 | if j > i { | |
451 | let l = if let Some(ref label) = next.label { | |
452 | label.len() + 2 | |
453 | } else { | |
454 | 0 | |
455 | }; | |
cc61c64b | 456 | if (overlaps(next, annotation, l) // Do not allow two labels to be in the same |
8bb4bdeb XL |
457 | // line if they overlap including padding, to |
458 | // avoid situations like: | |
459 | // | |
460 | // fn foo(x: u32) { | |
461 | // -------^------ | |
462 | // | | | |
463 | // fn_spanx_span | |
464 | // | |
8bb4bdeb | 465 | && annotation.has_label() // Both labels must have some text, otherwise |
cc61c64b XL |
466 | && next.has_label()) // they are not overlapping. |
467 | // Do not add a new line if this annotation | |
468 | // or the next are vertical line placeholders. | |
469 | || (annotation.takes_space() // If either this or the next annotation is | |
470 | && next.has_label()) // multiline start/end, move it to a new line | |
471 | || (annotation.has_label() // so as not to overlap the orizontal lines. | |
472 | && next.takes_space()) | |
041b39d2 XL |
473 | || (annotation.takes_space() && next.takes_space()) |
474 | || (overlaps(next, annotation, l) | |
475 | && next.end_col <= annotation.end_col | |
476 | && next.has_label() | |
477 | && p == 0) // Avoid #42595. | |
8bb4bdeb | 478 | { |
cc61c64b | 479 | // This annotation needs a new line in the output. |
8bb4bdeb XL |
480 | p += 1; |
481 | break; | |
482 | } | |
476ff2be SL |
483 | } |
484 | } | |
485 | if line_len < p { | |
486 | line_len = p; | |
487 | } | |
488 | } | |
cc61c64b | 489 | |
476ff2be SL |
490 | if line_len != 0 { |
491 | line_len += 1; | |
5bcae85e SL |
492 | } |
493 | ||
476ff2be SL |
494 | // If there are no annotations or the only annotations on this line are |
495 | // MultilineLine, then there's only code being shown, stop processing. | |
496 | if line.annotations.is_empty() || line.annotations.iter() | |
cc61c64b | 497 | .filter(|a| !a.is_line()).collect::<Vec<_>>().len() == 0 |
476ff2be | 498 | { |
cc61c64b | 499 | return vec![]; |
5bcae85e SL |
500 | } |
501 | ||
32a655c1 SL |
502 | // Write the colunmn separator. |
503 | // | |
504 | // After this we will have: | |
505 | // | |
506 | // 2 | fn foo() { | |
507 | // | | |
508 | // | | |
509 | // | | |
510 | // 3 | | |
511 | // 4 | } | |
512 | // | | |
476ff2be SL |
513 | for pos in 0..line_len + 1 { |
514 | draw_col_separator(buffer, line_offset + pos + 1, width_offset - 2); | |
515 | buffer.putc(line_offset + pos + 1, | |
516 | width_offset - 2, | |
517 | '|', | |
518 | Style::LineNumber); | |
519 | } | |
520 | ||
521 | // Write the horizontal lines for multiline annotations | |
522 | // (only the first and last lines need this). | |
523 | // | |
524 | // After this we will have: | |
525 | // | |
526 | // 2 | fn foo() { | |
527 | // | __________ | |
528 | // | | |
529 | // | | |
530 | // 3 | | |
531 | // 4 | } | |
532 | // | _ | |
533 | for &(pos, annotation) in &annotations_position { | |
534 | let style = if annotation.is_primary { | |
535 | Style::UnderlinePrimary | |
536 | } else { | |
537 | Style::UnderlineSecondary | |
538 | }; | |
539 | let pos = pos + 1; | |
540 | match annotation.annotation_type { | |
541 | AnnotationType::MultilineStart(depth) | | |
542 | AnnotationType::MultilineEnd(depth) => { | |
543 | draw_range(buffer, | |
544 | '_', | |
545 | line_offset + pos, | |
546 | width_offset + depth, | |
547 | code_offset + annotation.start_col, | |
548 | style); | |
549 | } | |
550 | _ => (), | |
551 | } | |
552 | } | |
553 | ||
cc61c64b | 554 | // Write the vertical lines for labels that are on a different line as the underline. |
476ff2be SL |
555 | // |
556 | // After this we will have: | |
557 | // | |
558 | // 2 | fn foo() { | |
559 | // | __________ | |
560 | // | | | | |
561 | // | | | |
cc61c64b | 562 | // 3 | |
476ff2be SL |
563 | // 4 | | } |
564 | // | |_ | |
565 | for &(pos, annotation) in &annotations_position { | |
566 | let style = if annotation.is_primary { | |
567 | Style::UnderlinePrimary | |
568 | } else { | |
569 | Style::UnderlineSecondary | |
570 | }; | |
571 | let pos = pos + 1; | |
32a655c1 | 572 | |
cc61c64b | 573 | if pos > 1 && (annotation.has_label() || annotation.takes_space()) { |
476ff2be SL |
574 | for p in line_offset + 1..line_offset + pos + 1 { |
575 | buffer.putc(p, | |
576 | code_offset + annotation.start_col, | |
5bcae85e | 577 | '|', |
476ff2be SL |
578 | style); |
579 | } | |
580 | } | |
581 | match annotation.annotation_type { | |
582 | AnnotationType::MultilineStart(depth) => { | |
583 | for p in line_offset + pos + 1..line_offset + line_len + 2 { | |
584 | buffer.putc(p, | |
585 | width_offset + depth - 1, | |
586 | '|', | |
587 | style); | |
588 | } | |
589 | } | |
590 | AnnotationType::MultilineEnd(depth) => { | |
591 | for p in line_offset..line_offset + pos + 1 { | |
592 | buffer.putc(p, | |
593 | width_offset + depth - 1, | |
594 | '|', | |
595 | style); | |
596 | } | |
597 | } | |
476ff2be SL |
598 | _ => (), |
599 | } | |
600 | } | |
601 | ||
602 | // Write the labels on the annotations that actually have a label. | |
603 | // | |
604 | // After this we will have: | |
605 | // | |
606 | // 2 | fn foo() { | |
cc61c64b XL |
607 | // | __________ |
608 | // | | | |
609 | // | something about `foo` | |
610 | // 3 | | |
611 | // 4 | } | |
612 | // | _ test | |
476ff2be SL |
613 | for &(pos, annotation) in &annotations_position { |
614 | let style = if annotation.is_primary { | |
615 | Style::LabelPrimary | |
616 | } else { | |
617 | Style::LabelSecondary | |
618 | }; | |
619 | let (pos, col) = if pos == 0 { | |
620 | (pos + 1, annotation.end_col + 1) | |
621 | } else { | |
622 | (pos + 2, annotation.start_col) | |
623 | }; | |
624 | if let Some(ref label) = annotation.label { | |
625 | buffer.puts(line_offset + pos, | |
626 | code_offset + col, | |
627 | &label, | |
628 | style); | |
629 | } | |
630 | } | |
631 | ||
632 | // Sort from biggest span to smallest span so that smaller spans are | |
633 | // represented in the output: | |
634 | // | |
635 | // x | fn foo() | |
636 | // | ^^^---^^ | |
637 | // | | | | |
638 | // | | something about `foo` | |
639 | // | something about `fn foo()` | |
640 | annotations_position.sort_by(|a, b| { | |
476ff2be | 641 | // Decreasing order |
32a655c1 | 642 | a.1.len().cmp(&b.1.len()).reverse() |
476ff2be | 643 | }); |
5bcae85e | 644 | |
476ff2be SL |
645 | // Write the underlines. |
646 | // | |
647 | // After this we will have: | |
648 | // | |
649 | // 2 | fn foo() { | |
cc61c64b XL |
650 | // | ____-_____^ |
651 | // | | | |
652 | // | something about `foo` | |
653 | // 3 | | |
654 | // 4 | } | |
655 | // | _^ test | |
476ff2be SL |
656 | for &(_, annotation) in &annotations_position { |
657 | let (underline, style) = if annotation.is_primary { | |
658 | ('^', Style::UnderlinePrimary) | |
5bcae85e | 659 | } else { |
476ff2be SL |
660 | ('-', Style::UnderlineSecondary) |
661 | }; | |
662 | for p in annotation.start_col..annotation.end_col { | |
663 | buffer.putc(line_offset + 1, | |
664 | code_offset + p, | |
665 | underline, | |
666 | style); | |
c1a9b12d SL |
667 | } |
668 | } | |
cc61c64b XL |
669 | annotations_position.iter().filter_map(|&(_, annotation)| { |
670 | match annotation.annotation_type { | |
671 | AnnotationType::MultilineStart(p) | AnnotationType::MultilineEnd(p) => { | |
672 | let style = if annotation.is_primary { | |
673 | Style::LabelPrimary | |
674 | } else { | |
675 | Style::LabelSecondary | |
676 | }; | |
677 | Some((p, style)) | |
abe05a73 | 678 | } |
cc61c64b XL |
679 | _ => None |
680 | } | |
681 | ||
682 | }).collect::<Vec<_>>() | |
5bcae85e SL |
683 | } |
684 | ||
685 | fn get_multispan_max_line_num(&mut self, msp: &MultiSpan) -> usize { | |
686 | let mut max = 0; | |
687 | if let Some(ref cm) = self.cm { | |
688 | for primary_span in msp.primary_spans() { | |
cc61c64b | 689 | if primary_span != &DUMMY_SP { |
ea8adc8c | 690 | let hi = cm.lookup_char_pos(primary_span.hi()); |
5bcae85e SL |
691 | if hi.line > max { |
692 | max = hi.line; | |
693 | } | |
694 | } | |
695 | } | |
abe05a73 XL |
696 | if !self.short_message { |
697 | for span_label in msp.span_labels() { | |
698 | if span_label.span != DUMMY_SP { | |
699 | let hi = cm.lookup_char_pos(span_label.span.hi()); | |
700 | if hi.line > max { | |
701 | max = hi.line; | |
702 | } | |
5bcae85e | 703 | } |
a7813a04 | 704 | } |
7453a54e | 705 | } |
c1a9b12d | 706 | } |
5bcae85e | 707 | max |
c1a9b12d SL |
708 | } |
709 | ||
9e0c209e | 710 | fn get_max_line_num(&mut self, span: &MultiSpan, children: &Vec<SubDiagnostic>) -> usize { |
5bcae85e | 711 | let mut max = 0; |
3157f602 | 712 | |
9e0c209e | 713 | let primary = self.get_multispan_max_line_num(span); |
5bcae85e | 714 | max = if primary > max { primary } else { max }; |
c1a9b12d | 715 | |
9e0c209e | 716 | for sub in children { |
5bcae85e SL |
717 | let sub_result = self.get_multispan_max_line_num(&sub.span); |
718 | max = if sub_result > max { primary } else { max }; | |
c1a9b12d | 719 | } |
5bcae85e | 720 | max |
c1a9b12d SL |
721 | } |
722 | ||
9e0c209e SL |
723 | // This "fixes" MultiSpans that contain Spans that are pointing to locations inside of |
724 | // <*macros>. Since these locations are often difficult to read, we move these Spans from | |
725 | // <*macros> to their corresponding use site. | |
726 | fn fix_multispan_in_std_macros(&mut self, span: &mut MultiSpan) -> bool { | |
727 | let mut spans_updated = false; | |
728 | ||
729 | if let Some(ref cm) = self.cm { | |
730 | let mut before_after: Vec<(Span, Span)> = vec![]; | |
731 | let mut new_labels: Vec<(Span, String)> = vec![]; | |
732 | ||
733 | // First, find all the spans in <*macros> and point instead at their use site | |
734 | for sp in span.primary_spans() { | |
cc61c64b | 735 | if *sp == DUMMY_SP { |
9e0c209e SL |
736 | continue; |
737 | } | |
7cac9316 XL |
738 | let call_sp = cm.call_span_if_macro(*sp); |
739 | if call_sp != *sp { | |
740 | before_after.push((sp.clone(), call_sp)); | |
9e0c209e | 741 | } |
cc61c64b | 742 | for trace in sp.macro_backtrace().iter().rev() { |
9e0c209e SL |
743 | // Only show macro locations that are local |
744 | // and display them like a span_note | |
745 | if let Some(def_site) = trace.def_site_span { | |
cc61c64b | 746 | if def_site == DUMMY_SP { |
9e0c209e SL |
747 | continue; |
748 | } | |
749 | // Check to make sure we're not in any <*macros> | |
750 | if !cm.span_to_filename(def_site).contains("macros>") && | |
c30ab7b3 | 751 | !trace.macro_decl_name.starts_with("#[") { |
9e0c209e SL |
752 | new_labels.push((trace.call_site, |
753 | "in this macro invocation".to_string())); | |
754 | break; | |
755 | } | |
756 | } | |
757 | } | |
758 | } | |
759 | for (label_span, label_text) in new_labels { | |
760 | span.push_span_label(label_span, label_text); | |
761 | } | |
762 | for sp_label in span.span_labels() { | |
cc61c64b | 763 | if sp_label.span == DUMMY_SP { |
9e0c209e SL |
764 | continue; |
765 | } | |
766 | if cm.span_to_filename(sp_label.span.clone()).contains("macros>") { | |
cc61c64b | 767 | let v = sp_label.span.macro_backtrace(); |
9e0c209e SL |
768 | if let Some(use_site) = v.last() { |
769 | before_after.push((sp_label.span.clone(), use_site.call_site.clone())); | |
770 | } | |
771 | } | |
772 | } | |
773 | // After we have them, make sure we replace these 'bad' def sites with their use sites | |
774 | for (before, after) in before_after { | |
775 | span.replace(before, after); | |
776 | spans_updated = true; | |
777 | } | |
778 | } | |
779 | ||
780 | spans_updated | |
781 | } | |
782 | ||
783 | // This does a small "fix" for multispans by looking to see if it can find any that | |
784 | // point directly at <*macros>. Since these are often difficult to read, this | |
785 | // will change the span to point at the use site. | |
786 | fn fix_multispans_in_std_macros(&mut self, | |
787 | span: &mut MultiSpan, | |
788 | children: &mut Vec<SubDiagnostic>) { | |
789 | let mut spans_updated = self.fix_multispan_in_std_macros(span); | |
790 | for child in children.iter_mut() { | |
791 | spans_updated |= self.fix_multispan_in_std_macros(&mut child.span); | |
792 | } | |
793 | if spans_updated { | |
794 | children.push(SubDiagnostic { | |
795 | level: Level::Note, | |
32a655c1 SL |
796 | message: vec![("this error originates in a macro outside of the current crate" |
797 | .to_string(), Style::NoStyle)], | |
9e0c209e | 798 | span: MultiSpan::new(), |
c30ab7b3 | 799 | render_span: None, |
9e0c209e SL |
800 | }); |
801 | } | |
802 | } | |
803 | ||
32a655c1 SL |
804 | /// Add a left margin to every line but the first, given a padding length and the label being |
805 | /// displayed, keeping the provided highlighting. | |
806 | fn msg_to_buffer(&self, | |
807 | buffer: &mut StyledBuffer, | |
7cac9316 | 808 | msg: &[(String, Style)], |
32a655c1 SL |
809 | padding: usize, |
810 | label: &str, | |
811 | override_style: Option<Style>) { | |
812 | ||
813 | // The extra 5 ` ` is padding that's always needed to align to the `note: `: | |
814 | // | |
815 | // error: message | |
816 | // --> file.rs:13:20 | |
817 | // | | |
818 | // 13 | <CODE> | |
819 | // | ^^^^ | |
820 | // | | |
821 | // = note: multiline | |
822 | // message | |
823 | // ++^^^----xx | |
824 | // | | | | | |
825 | // | | | magic `2` | |
826 | // | | length of label | |
827 | // | magic `3` | |
828 | // `max_line_num_len` | |
829 | let padding = (0..padding + label.len() + 5) | |
830 | .map(|_| " ") | |
831 | .collect::<String>(); | |
832 | ||
3b2f2976 | 833 | /// Return whether `style`, or the override if present and the style is `NoStyle`. |
32a655c1 SL |
834 | fn style_or_override(style: Style, override_style: Option<Style>) -> Style { |
835 | if let Some(o) = override_style { | |
836 | if style == Style::NoStyle { | |
837 | return o; | |
838 | } | |
839 | } | |
840 | style | |
841 | } | |
842 | ||
843 | let mut line_number = 0; | |
844 | ||
845 | // Provided the following diagnostic message: | |
846 | // | |
847 | // let msg = vec![ | |
848 | // (" | |
849 | // ("highlighted multiline\nstring to\nsee how it ", Style::NoStyle), | |
850 | // ("looks", Style::Highlight), | |
851 | // ("with\nvery ", Style::NoStyle), | |
852 | // ("weird", Style::Highlight), | |
853 | // (" formats\n", Style::NoStyle), | |
854 | // ("see?", Style::Highlight), | |
855 | // ]; | |
856 | // | |
857 | // the expected output on a note is (* surround the highlighted text) | |
858 | // | |
859 | // = note: highlighted multiline | |
860 | // string to | |
861 | // see how it *looks* with | |
862 | // very *weird* formats | |
863 | // see? | |
864 | for &(ref text, ref style) in msg.iter() { | |
865 | let lines = text.split('\n').collect::<Vec<_>>(); | |
866 | if lines.len() > 1 { | |
867 | for (i, line) in lines.iter().enumerate() { | |
868 | if i != 0 { | |
869 | line_number += 1; | |
870 | buffer.append(line_number, &padding, Style::NoStyle); | |
871 | } | |
872 | buffer.append(line_number, line, style_or_override(*style, override_style)); | |
873 | } | |
874 | } else { | |
875 | buffer.append(line_number, text, style_or_override(*style, override_style)); | |
876 | } | |
877 | } | |
878 | } | |
879 | ||
5bcae85e SL |
880 | fn emit_message_default(&mut self, |
881 | msp: &MultiSpan, | |
32a655c1 | 882 | msg: &Vec<(String, Style)>, |
abe05a73 | 883 | code: &Option<DiagnosticId>, |
5bcae85e SL |
884 | level: &Level, |
885 | max_line_num_len: usize, | |
886 | is_secondary: bool) | |
887 | -> io::Result<()> { | |
888 | let mut buffer = StyledBuffer::new(); | |
889 | ||
abe05a73 XL |
890 | if msp.primary_spans().is_empty() && msp.span_labels().is_empty() && is_secondary |
891 | && !self.short_message { | |
5bcae85e SL |
892 | // This is a secondary message with no span info |
893 | for _ in 0..max_line_num_len { | |
894 | buffer.prepend(0, " ", Style::NoStyle); | |
895 | } | |
896 | draw_note_separator(&mut buffer, 0, max_line_num_len + 1); | |
897 | buffer.append(0, &level.to_string(), Style::HeaderMsg); | |
898 | buffer.append(0, ": ", Style::NoStyle); | |
32a655c1 | 899 | self.msg_to_buffer(&mut buffer, msg, max_line_num_len, "note", None); |
c30ab7b3 | 900 | } else { |
5bcae85e | 901 | buffer.append(0, &level.to_string(), Style::Level(level.clone())); |
abe05a73 XL |
902 | // only render error codes, not lint codes |
903 | if let Some(DiagnosticId::Error(ref code)) = *code { | |
904 | buffer.append(0, "[", Style::Level(level.clone())); | |
905 | buffer.append(0, &code, Style::Level(level.clone())); | |
906 | buffer.append(0, "]", Style::Level(level.clone())); | |
5bcae85e SL |
907 | } |
908 | buffer.append(0, ": ", Style::HeaderMsg); | |
32a655c1 SL |
909 | for &(ref text, _) in msg.iter() { |
910 | buffer.append(0, text, Style::HeaderMsg); | |
911 | } | |
5bcae85e SL |
912 | } |
913 | ||
914 | // Preprocess all the annotations so that they are grouped by file and by line number | |
915 | // This helps us quickly iterate over the whole message (including secondary file spans) | |
916 | let mut annotated_files = self.preprocess_annotations(msp); | |
a7813a04 | 917 | |
5bcae85e | 918 | // Make sure our primary file comes first |
041b39d2 | 919 | let (primary_lo, cm) = if let (Some(cm), Some(ref primary_span)) = |
c30ab7b3 | 920 | (self.cm.as_ref(), msp.primary_span().as_ref()) { |
cc61c64b | 921 | if primary_span != &&DUMMY_SP { |
ea8adc8c | 922 | (cm.lookup_char_pos(primary_span.lo()), cm) |
5bcae85e | 923 | } else { |
abe05a73 | 924 | emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?; |
5bcae85e | 925 | return Ok(()); |
c30ab7b3 SL |
926 | } |
927 | } else { | |
928 | // If we don't have span information, emit and exit | |
abe05a73 | 929 | emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?; |
c30ab7b3 SL |
930 | return Ok(()); |
931 | }; | |
5bcae85e | 932 | if let Ok(pos) = |
c30ab7b3 | 933 | annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name)) { |
5bcae85e SL |
934 | annotated_files.swap(0, pos); |
935 | } | |
936 | ||
937 | // Print out the annotate source lines that correspond with the error | |
938 | for annotated_file in annotated_files { | |
7cac9316 | 939 | // we can't annotate anything if the source is unavailable. |
041b39d2 | 940 | if !cm.ensure_filemap_source_present(annotated_file.file.clone()) { |
7cac9316 XL |
941 | continue; |
942 | } | |
943 | ||
5bcae85e SL |
944 | // print out the span location and spacer before we print the annotated source |
945 | // to do this, we need to know if this span will be primary | |
946 | let is_primary = primary_lo.file.name == annotated_file.file.name; | |
947 | if is_primary { | |
5bcae85e | 948 | let loc = primary_lo.clone(); |
abe05a73 XL |
949 | if !self.short_message { |
950 | // remember where we are in the output buffer for easy reference | |
951 | let buffer_msg_line_offset = buffer.num_lines(); | |
952 | ||
953 | buffer.prepend(buffer_msg_line_offset, "--> ", Style::LineNumber); | |
954 | buffer.append(buffer_msg_line_offset, | |
955 | &format!("{}:{}:{}", loc.file.name, loc.line, loc.col.0 + 1), | |
956 | Style::LineAndColumn); | |
957 | for _ in 0..max_line_num_len { | |
958 | buffer.prepend(buffer_msg_line_offset, " ", Style::NoStyle); | |
959 | } | |
960 | } else { | |
961 | buffer.prepend(0, | |
962 | &format!("{}:{}:{} - ", loc.file.name, loc.line, loc.col.0 + 1), | |
963 | Style::LineAndColumn); | |
5bcae85e | 964 | } |
abe05a73 | 965 | } else if !self.short_message { |
5bcae85e SL |
966 | // remember where we are in the output buffer for easy reference |
967 | let buffer_msg_line_offset = buffer.num_lines(); | |
968 | ||
969 | // Add spacing line | |
970 | draw_col_separator(&mut buffer, buffer_msg_line_offset, max_line_num_len + 1); | |
971 | ||
972 | // Then, the secondary file indicator | |
973 | buffer.prepend(buffer_msg_line_offset + 1, "::: ", Style::LineNumber); | |
974 | buffer.append(buffer_msg_line_offset + 1, | |
c30ab7b3 SL |
975 | &annotated_file.file.name, |
976 | Style::LineAndColumn); | |
5bcae85e SL |
977 | for _ in 0..max_line_num_len { |
978 | buffer.prepend(buffer_msg_line_offset + 1, " ", Style::NoStyle); | |
c1a9b12d | 979 | } |
a7813a04 | 980 | } |
c1a9b12d | 981 | |
abe05a73 XL |
982 | if !self.short_message { |
983 | // Put in the spacer between the location and annotated source | |
984 | let buffer_msg_line_offset = buffer.num_lines(); | |
985 | draw_col_separator_no_space(&mut buffer, | |
986 | buffer_msg_line_offset, | |
987 | max_line_num_len + 1); | |
5bcae85e | 988 | |
abe05a73 XL |
989 | // Contains the vertical lines' positions for active multiline annotations |
990 | let mut multilines = HashMap::new(); | |
cc61c64b | 991 | |
abe05a73 XL |
992 | // Next, output the annotate source for this file |
993 | for line_idx in 0..annotated_file.lines.len() { | |
994 | let previous_buffer_line = buffer.num_lines(); | |
cc61c64b | 995 | |
abe05a73 XL |
996 | let width_offset = 3 + max_line_num_len; |
997 | let code_offset = if annotated_file.multiline_depth == 0 { | |
998 | width_offset | |
999 | } else { | |
1000 | width_offset + annotated_file.multiline_depth + 1 | |
1001 | }; | |
cc61c64b | 1002 | |
abe05a73 XL |
1003 | let depths = self.render_source_line(&mut buffer, |
1004 | annotated_file.file.clone(), | |
1005 | &annotated_file.lines[line_idx], | |
1006 | width_offset, | |
1007 | code_offset); | |
cc61c64b | 1008 | |
abe05a73 | 1009 | let mut to_add = HashMap::new(); |
5bcae85e | 1010 | |
abe05a73 XL |
1011 | for (depth, style) in depths { |
1012 | if multilines.get(&depth).is_some() { | |
1013 | multilines.remove(&depth); | |
1014 | } else { | |
1015 | to_add.insert(depth, style); | |
1016 | } | |
cc61c64b | 1017 | } |
cc61c64b | 1018 | |
abe05a73 XL |
1019 | // Set the multiline annotation vertical lines to the left of |
1020 | // the code in this line. | |
1021 | for (depth, style) in &multilines { | |
1022 | for line in previous_buffer_line..buffer.num_lines() { | |
cc61c64b | 1023 | draw_multiline_line(&mut buffer, |
abe05a73 | 1024 | line, |
cc61c64b XL |
1025 | width_offset, |
1026 | *depth, | |
1027 | *style); | |
1028 | } | |
abe05a73 XL |
1029 | } |
1030 | // check to see if we need to print out or elide lines that come between | |
1031 | // this annotated line and the next one. | |
1032 | if line_idx < (annotated_file.lines.len() - 1) { | |
1033 | let line_idx_delta = annotated_file.lines[line_idx + 1].line_index - | |
1034 | annotated_file.lines[line_idx].line_index; | |
1035 | if line_idx_delta > 2 { | |
1036 | let last_buffer_line_num = buffer.num_lines(); | |
1037 | buffer.puts(last_buffer_line_num, 0, "...", Style::LineNumber); | |
1038 | ||
1039 | // Set the multiline annotation vertical lines on `...` bridging line. | |
1040 | for (depth, style) in &multilines { | |
1041 | draw_multiline_line(&mut buffer, | |
1042 | last_buffer_line_num, | |
1043 | width_offset, | |
1044 | *depth, | |
1045 | *style); | |
1046 | } | |
1047 | } else if line_idx_delta == 2 { | |
1048 | let unannotated_line = annotated_file.file | |
1049 | .get_line(annotated_file.lines[line_idx].line_index) | |
1050 | .unwrap_or_else(|| Cow::from("")); | |
1051 | ||
1052 | let last_buffer_line_num = buffer.num_lines(); | |
1053 | ||
1054 | buffer.puts(last_buffer_line_num, | |
1055 | 0, | |
1056 | &(annotated_file.lines[line_idx + 1].line_index - 1) | |
1057 | .to_string(), | |
1058 | Style::LineNumber); | |
1059 | draw_col_separator(&mut buffer, | |
1060 | last_buffer_line_num, | |
1061 | 1 + max_line_num_len); | |
1062 | buffer.puts(last_buffer_line_num, | |
1063 | code_offset, | |
1064 | &unannotated_line, | |
1065 | Style::Quotation); | |
1066 | ||
1067 | for (depth, style) in &multilines { | |
1068 | draw_multiline_line(&mut buffer, | |
1069 | last_buffer_line_num, | |
1070 | width_offset, | |
1071 | *depth, | |
1072 | *style); | |
1073 | } | |
cc61c64b | 1074 | } |
c1a9b12d | 1075 | } |
cc61c64b | 1076 | |
abe05a73 XL |
1077 | multilines.extend(&to_add); |
1078 | } | |
7453a54e SL |
1079 | } |
1080 | } | |
5bcae85e | 1081 | |
5bcae85e | 1082 | // final step: take our styled buffer, render it, then output it |
abe05a73 | 1083 | emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?; |
5bcae85e SL |
1084 | |
1085 | Ok(()) | |
abe05a73 | 1086 | |
5bcae85e SL |
1087 | } |
1088 | fn emit_suggestion_default(&mut self, | |
1089 | suggestion: &CodeSuggestion, | |
1090 | level: &Level, | |
5bcae85e SL |
1091 | max_line_num_len: usize) |
1092 | -> io::Result<()> { | |
1093 | use std::borrow::Borrow; | |
1094 | ||
5bcae85e SL |
1095 | if let Some(ref cm) = self.cm { |
1096 | let mut buffer = StyledBuffer::new(); | |
1097 | ||
abe05a73 | 1098 | // Render the suggestion message |
7cac9316 XL |
1099 | buffer.append(0, &level.to_string(), Style::Level(level.clone())); |
1100 | buffer.append(0, ": ", Style::HeaderMsg); | |
1101 | self.msg_to_buffer(&mut buffer, | |
041b39d2 XL |
1102 | &[(suggestion.msg.to_owned(), Style::NoStyle)], |
1103 | max_line_num_len, | |
1104 | "suggestion", | |
1105 | Some(Style::HeaderMsg)); | |
5bcae85e | 1106 | |
abe05a73 | 1107 | // Render the replacements for each suggestion |
7cac9316 | 1108 | let suggestions = suggestion.splice_lines(cm.borrow()); |
abe05a73 | 1109 | |
041b39d2 | 1110 | let mut row_num = 2; |
abe05a73 XL |
1111 | for &(ref complete, ref parts) in suggestions.iter().take(MAX_SUGGESTIONS) { |
1112 | let show_underline = parts.len() == 1 | |
1113 | && complete.lines().count() == 1 | |
1114 | && parts[0].snippet.trim() != complete.trim(); | |
1115 | ||
1116 | let lines = cm.span_to_lines(parts[0].span).unwrap(); | |
1117 | ||
1118 | assert!(!lines.lines.is_empty()); | |
1119 | ||
1120 | let span_start_pos = cm.lookup_char_pos(parts[0].span.lo()); | |
1121 | let line_start = span_start_pos.line; | |
1122 | draw_col_separator_no_space(&mut buffer, 1, max_line_num_len + 1); | |
041b39d2 XL |
1123 | let mut line_pos = 0; |
1124 | // Only show underline if there's a single suggestion and it is a single line | |
7cac9316 XL |
1125 | let mut lines = complete.lines(); |
1126 | for line in lines.by_ref().take(MAX_HIGHLIGHT_LINES) { | |
041b39d2 XL |
1127 | // Print the span column to avoid confusion |
1128 | buffer.puts(row_num, | |
1129 | 0, | |
1130 | &((line_start + line_pos).to_string()), | |
1131 | Style::LineNumber); | |
1132 | // print the suggestion | |
7cac9316 XL |
1133 | draw_col_separator(&mut buffer, row_num, max_line_num_len + 1); |
1134 | buffer.append(row_num, line, Style::NoStyle); | |
041b39d2 | 1135 | line_pos += 1; |
7cac9316 | 1136 | row_num += 1; |
abe05a73 XL |
1137 | } |
1138 | // Only show an underline in the suggestions if the suggestion is not the | |
1139 | // entirety of the code being shown and the displayed code is not multiline. | |
1140 | if show_underline { | |
1141 | draw_col_separator(&mut buffer, row_num, max_line_num_len + 1); | |
1142 | let start = parts[0].snippet.len() - parts[0].snippet.trim_left().len(); | |
1143 | let sub_len = parts[0].snippet.trim().len(); | |
1144 | let underline_start = span_start_pos.col.0 + start; | |
1145 | let underline_end = span_start_pos.col.0 + sub_len; | |
1146 | for p in underline_start..underline_end { | |
1147 | buffer.putc(row_num, | |
1148 | max_line_num_len + 3 + p, | |
1149 | '^', | |
1150 | Style::UnderlinePrimary); | |
041b39d2 | 1151 | } |
abe05a73 | 1152 | row_num += 1; |
7cac9316 | 1153 | } |
5bcae85e | 1154 | |
7cac9316 XL |
1155 | // if we elided some lines, add an ellipsis |
1156 | if let Some(_) = lines.next() { | |
041b39d2 XL |
1157 | buffer.puts(row_num, max_line_num_len - 1, "...", Style::LineNumber); |
1158 | } else if !show_underline { | |
1159 | draw_col_separator_no_space(&mut buffer, row_num, max_line_num_len + 1); | |
1160 | row_num += 1; | |
7cac9316 XL |
1161 | } |
1162 | } | |
1163 | if suggestions.len() > MAX_SUGGESTIONS { | |
1164 | let msg = format!("and {} other candidates", suggestions.len() - MAX_SUGGESTIONS); | |
041b39d2 | 1165 | buffer.puts(row_num, 0, &msg, Style::NoStyle); |
7453a54e | 1166 | } |
abe05a73 | 1167 | emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?; |
c1a9b12d | 1168 | } |
7453a54e | 1169 | Ok(()) |
c1a9b12d | 1170 | } |
9e0c209e SL |
1171 | fn emit_messages_default(&mut self, |
1172 | level: &Level, | |
32a655c1 | 1173 | message: &Vec<(String, Style)>, |
abe05a73 | 1174 | code: &Option<DiagnosticId>, |
9e0c209e | 1175 | span: &MultiSpan, |
abe05a73 XL |
1176 | children: &Vec<SubDiagnostic>, |
1177 | suggestions: &[CodeSuggestion]) { | |
9e0c209e | 1178 | let max_line_num = self.get_max_line_num(span, children); |
5bcae85e SL |
1179 | let max_line_num_len = max_line_num.to_string().len(); |
1180 | ||
c30ab7b3 | 1181 | match self.emit_message_default(span, message, code, level, max_line_num_len, false) { |
5bcae85e | 1182 | Ok(()) => { |
9e0c209e | 1183 | if !children.is_empty() { |
5bcae85e | 1184 | let mut buffer = StyledBuffer::new(); |
abe05a73 XL |
1185 | if !self.short_message { |
1186 | draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1); | |
1187 | } | |
1188 | match emit_to_destination(&buffer.render(), level, &mut self.dst, | |
1189 | self.short_message) { | |
5bcae85e SL |
1190 | Ok(()) => (), |
1191 | Err(e) => panic!("failed to emit error: {}", e) | |
1192 | } | |
1193 | } | |
abe05a73 XL |
1194 | if !self.short_message { |
1195 | for child in children { | |
1196 | let span = child.render_span.as_ref().unwrap_or(&child.span); | |
1197 | match self.emit_message_default(&span, | |
1198 | &child.styled_message(), | |
1199 | &None, | |
1200 | &child.level, | |
1201 | max_line_num_len, | |
1202 | true) { | |
1203 | Err(e) => panic!("failed to emit error: {}", e), | |
1204 | _ => () | |
1205 | } | |
1206 | } | |
1207 | for sugg in suggestions { | |
1208 | match self.emit_suggestion_default(sugg, | |
1209 | &Level::Help, | |
1210 | max_line_num_len) { | |
1211 | Err(e) => panic!("failed to emit error: {}", e), | |
1212 | _ => () | |
5bcae85e SL |
1213 | } |
1214 | } | |
1215 | } | |
1216 | } | |
c30ab7b3 | 1217 | Err(e) => panic!("failed to emit error: {}", e), |
5bcae85e SL |
1218 | } |
1219 | match write!(&mut self.dst, "\n") { | |
1220 | Err(e) => panic!("failed to emit error: {}", e), | |
c30ab7b3 SL |
1221 | _ => { |
1222 | match self.dst.flush() { | |
1223 | Err(e) => panic!("failed to emit error: {}", e), | |
1224 | _ => (), | |
1225 | } | |
9cc50fc6 | 1226 | } |
c1a9b12d | 1227 | } |
c1a9b12d | 1228 | } |
1a4d82fc | 1229 | } |
970d7e83 | 1230 | |
5bcae85e SL |
1231 | fn draw_col_separator(buffer: &mut StyledBuffer, line: usize, col: usize) { |
1232 | buffer.puts(line, col, "| ", Style::LineNumber); | |
7453a54e SL |
1233 | } |
1234 | ||
5bcae85e | 1235 | fn draw_col_separator_no_space(buffer: &mut StyledBuffer, line: usize, col: usize) { |
476ff2be SL |
1236 | draw_col_separator_no_space_with_style(buffer, line, col, Style::LineNumber); |
1237 | } | |
1238 | ||
1239 | fn draw_col_separator_no_space_with_style(buffer: &mut StyledBuffer, | |
1240 | line: usize, | |
1241 | col: usize, | |
1242 | style: Style) { | |
1243 | buffer.putc(line, col, '|', style); | |
1244 | } | |
1245 | ||
1246 | fn draw_range(buffer: &mut StyledBuffer, symbol: char, line: usize, | |
1247 | col_from: usize, col_to: usize, style: Style) { | |
1248 | for col in col_from..col_to { | |
1249 | buffer.putc(line, col, symbol, style); | |
1250 | } | |
5bcae85e SL |
1251 | } |
1252 | ||
1253 | fn draw_note_separator(buffer: &mut StyledBuffer, line: usize, col: usize) { | |
1254 | buffer.puts(line, col, "= ", Style::LineNumber); | |
1255 | } | |
1256 | ||
cc61c64b XL |
1257 | fn draw_multiline_line(buffer: &mut StyledBuffer, |
1258 | line: usize, | |
1259 | offset: usize, | |
1260 | depth: usize, | |
1261 | style: Style) | |
1262 | { | |
1263 | buffer.putc(line, offset + depth - 1, '|', style); | |
1264 | } | |
1265 | ||
476ff2be SL |
1266 | fn num_overlap(a_start: usize, a_end: usize, b_start: usize, b_end:usize, inclusive: bool) -> bool { |
1267 | let extra = if inclusive { | |
1268 | 1 | |
1269 | } else { | |
1270 | 0 | |
1271 | }; | |
1272 | (b_start..b_end + extra).contains(a_start) || | |
1273 | (a_start..a_end + extra).contains(b_start) | |
1274 | } | |
8bb4bdeb XL |
1275 | fn overlaps(a1: &Annotation, a2: &Annotation, padding: usize) -> bool { |
1276 | num_overlap(a1.start_col, a1.end_col + padding, a2.start_col, a2.end_col, false) | |
5bcae85e SL |
1277 | } |
1278 | ||
1279 | fn emit_to_destination(rendered_buffer: &Vec<Vec<StyledString>>, | |
c30ab7b3 | 1280 | lvl: &Level, |
abe05a73 XL |
1281 | dst: &mut Destination, |
1282 | short_message: bool) | |
c30ab7b3 | 1283 | -> io::Result<()> { |
9e0c209e SL |
1284 | use lock; |
1285 | ||
1286 | // In order to prevent error message interleaving, where multiple error lines get intermixed | |
1287 | // when multiple compiler processes error simultaneously, we emit errors with additional | |
1288 | // steps. | |
1289 | // | |
1290 | // On Unix systems, we write into a buffered terminal rather than directly to a terminal. When | |
1291 | // the .flush() is called we take the buffer created from the buffered writes and write it at | |
1292 | // one shot. Because the Unix systems use ANSI for the colors, which is a text-based styling | |
1293 | // scheme, this buffered approach works and maintains the styling. | |
1294 | // | |
1295 | // On Windows, styling happens through calls to a terminal API. This prevents us from using the | |
1296 | // same buffering approach. Instead, we use a global Windows mutex, which we acquire long | |
1297 | // enough to output the full error message, then we release. | |
1298 | let _buffer_lock = lock::acquire_global_lock("rustc_errors"); | |
5bcae85e SL |
1299 | for line in rendered_buffer { |
1300 | for part in line { | |
1301 | dst.apply_style(lvl.clone(), part.style)?; | |
1302 | write!(dst, "{}", part.text)?; | |
1303 | dst.reset_attrs()?; | |
a7813a04 | 1304 | } |
abe05a73 XL |
1305 | if !short_message { |
1306 | write!(dst, "\n")?; | |
1307 | } | |
9cc50fc6 | 1308 | } |
9e0c209e | 1309 | dst.flush()?; |
9cc50fc6 SL |
1310 | Ok(()) |
1311 | } | |
1312 | ||
c34b1796 AL |
1313 | #[cfg(unix)] |
1314 | fn stderr_isatty() -> bool { | |
92a42be0 | 1315 | use libc; |
c34b1796 AL |
1316 | unsafe { libc::isatty(libc::STDERR_FILENO) != 0 } |
1317 | } | |
1318 | #[cfg(windows)] | |
1319 | fn stderr_isatty() -> bool { | |
92a42be0 SL |
1320 | type DWORD = u32; |
1321 | type BOOL = i32; | |
1322 | type HANDLE = *mut u8; | |
1323 | const STD_ERROR_HANDLE: DWORD = -12i32 as DWORD; | |
c34b1796 | 1324 | extern "system" { |
92a42be0 | 1325 | fn GetStdHandle(which: DWORD) -> HANDLE; |
c30ab7b3 | 1326 | fn GetConsoleMode(hConsoleHandle: HANDLE, lpMode: *mut DWORD) -> BOOL; |
c34b1796 AL |
1327 | } |
1328 | unsafe { | |
1329 | let handle = GetStdHandle(STD_ERROR_HANDLE); | |
1330 | let mut out = 0; | |
1331 | GetConsoleMode(handle, &mut out) != 0 | |
1332 | } | |
1333 | } | |
1334 | ||
9e0c209e SL |
1335 | pub type BufferedStderr = term::Terminal<Output = BufferedWriter> + Send; |
1336 | ||
5bcae85e | 1337 | pub enum Destination { |
9cc50fc6 | 1338 | Terminal(Box<term::StderrTerminal>), |
9e0c209e | 1339 | BufferedTerminal(Box<BufferedStderr>), |
9cc50fc6 SL |
1340 | Raw(Box<Write + Send>), |
1341 | } | |
1342 | ||
9e0c209e SL |
1343 | /// Buffered writer gives us a way on Unix to buffer up an entire error message before we output |
1344 | /// it. This helps to prevent interleaving of multiple error messages when multiple compiler | |
1345 | /// processes error simultaneously | |
1346 | pub struct BufferedWriter { | |
1347 | buffer: Vec<u8>, | |
1348 | } | |
1349 | ||
1350 | impl BufferedWriter { | |
1351 | // note: we use _new because the conditional compilation at its use site may make this | |
1352 | // this function unused on some platforms | |
1353 | fn _new() -> BufferedWriter { | |
c30ab7b3 | 1354 | BufferedWriter { buffer: vec![] } |
9e0c209e SL |
1355 | } |
1356 | } | |
1357 | ||
1358 | impl Write for BufferedWriter { | |
1359 | fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | |
1360 | for b in buf { | |
1361 | self.buffer.push(*b); | |
1362 | } | |
1363 | Ok(buf.len()) | |
1364 | } | |
1365 | fn flush(&mut self) -> io::Result<()> { | |
1366 | let mut stderr = io::stderr(); | |
7cac9316 XL |
1367 | let result = stderr.write_all(&self.buffer) |
1368 | .and_then(|_| stderr.flush()); | |
9e0c209e SL |
1369 | self.buffer.clear(); |
1370 | result | |
1371 | } | |
1372 | } | |
1373 | ||
9cc50fc6 | 1374 | impl Destination { |
9e0c209e SL |
1375 | #[cfg(not(windows))] |
1376 | /// When not on Windows, prefer the buffered terminal so that we can buffer an entire error | |
1377 | /// to be emitted at one time. | |
1378 | fn from_stderr() -> Destination { | |
c30ab7b3 | 1379 | let stderr: Option<Box<BufferedStderr>> = |
9e0c209e SL |
1380 | term::TerminfoTerminal::new(BufferedWriter::_new()) |
1381 | .map(|t| Box::new(t) as Box<BufferedStderr>); | |
1382 | ||
1383 | match stderr { | |
1384 | Some(t) => BufferedTerminal(t), | |
c30ab7b3 | 1385 | None => Raw(Box::new(io::stderr())), |
9e0c209e SL |
1386 | } |
1387 | } | |
1388 | ||
1389 | #[cfg(windows)] | |
1390 | /// Return a normal, unbuffered terminal when on Windows. | |
9cc50fc6 | 1391 | fn from_stderr() -> Destination { |
c30ab7b3 SL |
1392 | let stderr: Option<Box<term::StderrTerminal>> = term::TerminfoTerminal::new(io::stderr()) |
1393 | .map(|t| Box::new(t) as Box<term::StderrTerminal>) | |
1394 | .or_else(|| { | |
1395 | term::WinConsole::new(io::stderr()) | |
1396 | .ok() | |
1397 | .map(|t| Box::new(t) as Box<term::StderrTerminal>) | |
1398 | }); | |
9e0c209e SL |
1399 | |
1400 | match stderr { | |
9cc50fc6 | 1401 | Some(t) => Terminal(t), |
c30ab7b3 | 1402 | None => Raw(Box::new(io::stderr())), |
9cc50fc6 SL |
1403 | } |
1404 | } | |
1405 | ||
c30ab7b3 | 1406 | fn apply_style(&mut self, lvl: Level, style: Style) -> io::Result<()> { |
a7813a04 | 1407 | match style { |
041b39d2 | 1408 | Style::LineAndColumn => {} |
a7813a04 | 1409 | Style::LineNumber => { |
9e0c209e SL |
1410 | self.start_attr(term::Attr::Bold)?; |
1411 | if cfg!(windows) { | |
1412 | self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_CYAN))?; | |
1413 | } else { | |
1414 | self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_BLUE))?; | |
1415 | } | |
a7813a04 | 1416 | } |
5bcae85e | 1417 | Style::Quotation => {} |
041b39d2 | 1418 | Style::HeaderMsg => { |
9e0c209e SL |
1419 | self.start_attr(term::Attr::Bold)?; |
1420 | if cfg!(windows) { | |
1421 | self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_WHITE))?; | |
1422 | } | |
a7813a04 XL |
1423 | } |
1424 | Style::UnderlinePrimary | Style::LabelPrimary => { | |
9e0c209e SL |
1425 | self.start_attr(term::Attr::Bold)?; |
1426 | self.start_attr(term::Attr::ForegroundColor(lvl.color()))?; | |
a7813a04 | 1427 | } |
5bcae85e SL |
1428 | Style::UnderlineSecondary | |
1429 | Style::LabelSecondary => { | |
9e0c209e SL |
1430 | self.start_attr(term::Attr::Bold)?; |
1431 | if cfg!(windows) { | |
1432 | self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_CYAN))?; | |
1433 | } else { | |
1434 | self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_BLUE))?; | |
1435 | } | |
a7813a04 | 1436 | } |
5bcae85e SL |
1437 | Style::NoStyle => {} |
1438 | Style::Level(l) => { | |
9e0c209e SL |
1439 | self.start_attr(term::Attr::Bold)?; |
1440 | self.start_attr(term::Attr::ForegroundColor(l.color()))?; | |
a7813a04 | 1441 | } |
32a655c1 | 1442 | Style::Highlight => self.start_attr(term::Attr::Bold)?, |
a7813a04 XL |
1443 | } |
1444 | Ok(()) | |
1445 | } | |
1446 | ||
1447 | fn start_attr(&mut self, attr: term::Attr) -> io::Result<()> { | |
1448 | match *self { | |
c30ab7b3 SL |
1449 | Terminal(ref mut t) => { |
1450 | t.attr(attr)?; | |
1451 | } | |
1452 | BufferedTerminal(ref mut t) => { | |
1453 | t.attr(attr)?; | |
1454 | } | |
1455 | Raw(_) => {} | |
a7813a04 XL |
1456 | } |
1457 | Ok(()) | |
1458 | } | |
1459 | ||
1460 | fn reset_attrs(&mut self) -> io::Result<()> { | |
1461 | match *self { | |
c30ab7b3 SL |
1462 | Terminal(ref mut t) => { |
1463 | t.reset()?; | |
1464 | } | |
1465 | BufferedTerminal(ref mut t) => { | |
1466 | t.reset()?; | |
1467 | } | |
1468 | Raw(_) => {} | |
a7813a04 XL |
1469 | } |
1470 | Ok(()) | |
1471 | } | |
9cc50fc6 SL |
1472 | } |
1473 | ||
c34b1796 AL |
1474 | impl Write for Destination { |
1475 | fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { | |
1476 | match *self { | |
1477 | Terminal(ref mut t) => t.write(bytes), | |
9e0c209e | 1478 | BufferedTerminal(ref mut t) => t.write(bytes), |
c34b1796 AL |
1479 | Raw(ref mut w) => w.write(bytes), |
1480 | } | |
1481 | } | |
1482 | fn flush(&mut self) -> io::Result<()> { | |
1a4d82fc | 1483 | match *self { |
c34b1796 | 1484 | Terminal(ref mut t) => t.flush(), |
9e0c209e | 1485 | BufferedTerminal(ref mut t) => t.flush(), |
c34b1796 | 1486 | Raw(ref mut w) => w.flush(), |
1a4d82fc JJ |
1487 | } |
1488 | } | |
9e0c209e | 1489 | } |