]>
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 | ||
7453a54e | 13 | use codemap::{self, COMMAND_LINE_SP, DUMMY_SP, Pos, Span, MultiSpan}; |
1a4d82fc | 14 | use diagnostics; |
223e47cc | 15 | |
7453a54e | 16 | use errors::{Level, RenderSpan, CodeSuggestion, DiagnosticBuilder}; |
9cc50fc6 SL |
17 | use errors::RenderSpan::*; |
18 | use errors::Level::*; | |
19 | ||
20 | use std::{cmp, fmt}; | |
c34b1796 AL |
21 | use std::io::prelude::*; |
22 | use std::io; | |
9cc50fc6 | 23 | use std::rc::Rc; |
92a42be0 | 24 | use term; |
1a4d82fc | 25 | |
9cc50fc6 | 26 | pub trait Emitter { |
7453a54e SL |
27 | fn emit(&mut self, span: Option<&MultiSpan>, msg: &str, code: Option<&str>, lvl: Level); |
28 | fn custom_emit(&mut self, sp: &RenderSpan, msg: &str, lvl: Level); | |
9cc50fc6 SL |
29 | |
30 | /// Emit a structured diagnostic. | |
31 | fn emit_struct(&mut self, db: &DiagnosticBuilder) { | |
7453a54e | 32 | self.emit(db.span.as_ref(), &db.message, db.code.as_ref().map(|s| &**s), db.level); |
9cc50fc6 SL |
33 | for child in &db.children { |
34 | match child.render_span { | |
7453a54e SL |
35 | Some(ref sp) => self.custom_emit(sp, &child.message, child.level), |
36 | None => self.emit(child.span.as_ref(), &child.message, None, child.level), | |
9cc50fc6 | 37 | } |
1a4d82fc JJ |
38 | } |
39 | } | |
223e47cc LB |
40 | } |
41 | ||
9cc50fc6 | 42 | /// maximum number of lines we will print for each error; arbitrary. |
7453a54e SL |
43 | pub const MAX_HIGHLIGHT_LINES: usize = 6; |
44 | ||
45 | /// maximum number of lines we will print for each span; arbitrary. | |
46 | const MAX_SP_LINES: usize = 6; | |
9cc50fc6 SL |
47 | |
48 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | |
1a4d82fc JJ |
49 | pub enum ColorConfig { |
50 | Auto, | |
51 | Always, | |
9cc50fc6 | 52 | Never, |
223e47cc LB |
53 | } |
54 | ||
9cc50fc6 SL |
55 | impl ColorConfig { |
56 | fn use_color(&self) -> bool { | |
57 | match *self { | |
58 | ColorConfig::Always => true, | |
59 | ColorConfig::Never => false, | |
60 | ColorConfig::Auto => stderr_isatty(), | |
61 | } | |
d9579d0f AL |
62 | } |
63 | } | |
64 | ||
9cc50fc6 SL |
65 | /// A basic emitter for when we don't have access to a codemap or registry. Used |
66 | /// for reporting very early errors, etc. | |
67 | pub struct BasicEmitter { | |
68 | dst: Destination, | |
d9579d0f AL |
69 | } |
70 | ||
9cc50fc6 SL |
71 | impl Emitter for BasicEmitter { |
72 | fn emit(&mut self, | |
7453a54e | 73 | msp: Option<&MultiSpan>, |
9cc50fc6 SL |
74 | msg: &str, |
75 | code: Option<&str>, | |
76 | lvl: Level) { | |
7453a54e | 77 | assert!(msp.is_none(), "BasicEmitter can't handle spans"); |
9cc50fc6 SL |
78 | if let Err(e) = print_diagnostic(&mut self.dst, "", lvl, msg, code) { |
79 | panic!("failed to print diagnostics: {:?}", e); | |
80 | } | |
d9579d0f | 81 | |
d9579d0f | 82 | } |
223e47cc | 83 | |
7453a54e | 84 | fn custom_emit(&mut self, _: &RenderSpan, _: &str, _: Level) { |
9cc50fc6 | 85 | panic!("BasicEmitter can't handle custom_emit"); |
223e47cc LB |
86 | } |
87 | } | |
88 | ||
9cc50fc6 SL |
89 | impl BasicEmitter { |
90 | pub fn stderr(color_config: ColorConfig) -> BasicEmitter { | |
91 | if color_config.use_color() { | |
92 | let dst = Destination::from_stderr(); | |
93 | BasicEmitter { dst: dst } | |
94 | } else { | |
95 | BasicEmitter { dst: Raw(Box::new(io::stderr())) } | |
223e47cc | 96 | } |
223e47cc LB |
97 | } |
98 | } | |
99 | ||
9cc50fc6 SL |
100 | pub struct EmitterWriter { |
101 | dst: Destination, | |
102 | registry: Option<diagnostics::registry::Registry>, | |
103 | cm: Rc<codemap::CodeMap>, | |
223e47cc LB |
104 | } |
105 | ||
9cc50fc6 SL |
106 | impl Emitter for EmitterWriter { |
107 | fn emit(&mut self, | |
7453a54e | 108 | msp: Option<&MultiSpan>, |
9cc50fc6 SL |
109 | msg: &str, |
110 | code: Option<&str>, | |
111 | lvl: Level) { | |
7453a54e SL |
112 | let error = match msp.map(|s|(s.to_span_bounds(), s)) { |
113 | Some((COMMAND_LINE_SP, msp)) => { | |
114 | self.emit_(&FileLine(msp.clone()), msg, code, lvl) | |
115 | }, | |
116 | Some((DUMMY_SP, _)) | None => print_diagnostic(&mut self.dst, "", lvl, msg, code), | |
117 | Some((_, msp)) => self.emit_(&FullSpan(msp.clone()), msg, code, lvl), | |
9cc50fc6 | 118 | }; |
1a4d82fc | 119 | |
9cc50fc6 SL |
120 | if let Err(e) = error { |
121 | panic!("failed to print diagnostics: {:?}", e); | |
1a4d82fc JJ |
122 | } |
123 | } | |
223e47cc | 124 | |
9cc50fc6 | 125 | fn custom_emit(&mut self, |
7453a54e | 126 | rsp: &RenderSpan, |
9cc50fc6 SL |
127 | msg: &str, |
128 | lvl: Level) { | |
7453a54e | 129 | if let Err(e) = self.emit_(rsp, msg, None, lvl) { |
9cc50fc6 | 130 | panic!("failed to print diagnostics: {:?}", e); |
1a4d82fc | 131 | } |
223e47cc LB |
132 | } |
133 | } | |
134 | ||
c1a9b12d SL |
135 | /// Do not use this for messages that end in `\n` – use `println_maybe_styled` instead. See |
136 | /// `EmitterWriter::print_maybe_styled` for details. | |
137 | macro_rules! print_maybe_styled { | |
9cc50fc6 SL |
138 | ($dst: expr, $style: expr, $($arg: tt)*) => { |
139 | $dst.print_maybe_styled(format_args!($($arg)*), $style, false) | |
c1a9b12d SL |
140 | } |
141 | } | |
142 | ||
143 | macro_rules! println_maybe_styled { | |
9cc50fc6 SL |
144 | ($dst: expr, $style: expr, $($arg: tt)*) => { |
145 | $dst.print_maybe_styled(format_args!($($arg)*), $style, true) | |
c1a9b12d SL |
146 | } |
147 | } | |
148 | ||
1a4d82fc JJ |
149 | impl EmitterWriter { |
150 | pub fn stderr(color_config: ColorConfig, | |
9cc50fc6 SL |
151 | registry: Option<diagnostics::registry::Registry>, |
152 | code_map: Rc<codemap::CodeMap>) | |
153 | -> EmitterWriter { | |
154 | if color_config.use_color() { | |
155 | let dst = Destination::from_stderr(); | |
156 | EmitterWriter { dst: dst, registry: registry, cm: code_map } | |
1a4d82fc | 157 | } else { |
9cc50fc6 | 158 | EmitterWriter { dst: Raw(Box::new(io::stderr())), registry: registry, cm: code_map } |
1a4d82fc JJ |
159 | } |
160 | } | |
161 | ||
c34b1796 | 162 | pub fn new(dst: Box<Write + Send>, |
9cc50fc6 SL |
163 | registry: Option<diagnostics::registry::Registry>, |
164 | code_map: Rc<codemap::CodeMap>) | |
165 | -> EmitterWriter { | |
166 | EmitterWriter { dst: Raw(dst), registry: registry, cm: code_map } | |
c1a9b12d SL |
167 | } |
168 | ||
9cc50fc6 | 169 | fn emit_(&mut self, |
7453a54e | 170 | rsp: &RenderSpan, |
9cc50fc6 SL |
171 | msg: &str, |
172 | code: Option<&str>, | |
173 | lvl: Level) | |
174 | -> io::Result<()> { | |
7453a54e SL |
175 | let msp = rsp.span(); |
176 | let bounds = msp.to_span_bounds(); | |
c1a9b12d | 177 | |
7453a54e | 178 | let ss = if bounds == COMMAND_LINE_SP { |
c1a9b12d | 179 | "<command line option>".to_string() |
7453a54e SL |
180 | } else if let EndSpan(_) = *rsp { |
181 | let span_end = Span { lo: bounds.hi, hi: bounds.hi, expn_id: bounds.expn_id}; | |
9cc50fc6 | 182 | self.cm.span_to_string(span_end) |
c1a9b12d | 183 | } else { |
7453a54e | 184 | self.cm.span_to_string(bounds) |
c1a9b12d SL |
185 | }; |
186 | ||
54a0048b | 187 | print_diagnostic(&mut self.dst, &ss[..], lvl, msg, code)?; |
c1a9b12d | 188 | |
7453a54e | 189 | match *rsp { |
c1a9b12d | 190 | FullSpan(_) => { |
54a0048b SL |
191 | self.highlight_lines(msp, lvl)?; |
192 | self.print_macro_backtrace(bounds)?; | |
c1a9b12d SL |
193 | } |
194 | EndSpan(_) => { | |
54a0048b SL |
195 | self.end_highlight_lines(msp, lvl)?; |
196 | self.print_macro_backtrace(bounds)?; | |
c1a9b12d | 197 | } |
7453a54e | 198 | Suggestion(ref suggestion) => { |
54a0048b SL |
199 | self.highlight_suggestion(suggestion)?; |
200 | self.print_macro_backtrace(bounds)?; | |
c1a9b12d SL |
201 | } |
202 | FileLine(..) => { | |
203 | // no source text in this case! | |
204 | } | |
205 | } | |
206 | ||
7453a54e SL |
207 | if let Some(code) = code { |
208 | if let Some(_) = self.registry.as_ref() | |
209 | .and_then(|registry| registry.find_description(code)) { | |
54a0048b SL |
210 | print_diagnostic(&mut self.dst, &ss[..], Help, |
211 | &format!("run `rustc --explain {}` to see a \ | |
212 | detailed explanation", code), None)?; | |
7453a54e | 213 | } |
c1a9b12d SL |
214 | } |
215 | Ok(()) | |
216 | } | |
217 | ||
7453a54e | 218 | fn highlight_suggestion(&mut self, suggestion: &CodeSuggestion) -> io::Result<()> |
c1a9b12d | 219 | { |
7453a54e | 220 | let lines = self.cm.span_to_lines(suggestion.msp.to_span_bounds()).unwrap(); |
c1a9b12d SL |
221 | assert!(!lines.lines.is_empty()); |
222 | ||
7453a54e SL |
223 | let complete = suggestion.splice_lines(&self.cm); |
224 | let line_count = cmp::min(lines.lines.len(), MAX_HIGHLIGHT_LINES); | |
225 | let display_lines = &lines.lines[..line_count]; | |
c1a9b12d | 226 | |
7453a54e SL |
227 | let fm = &*lines.file; |
228 | // Calculate the widest number to format evenly | |
229 | let max_digits = line_num_max_digits(display_lines.last().unwrap()); | |
c1a9b12d SL |
230 | |
231 | // print the suggestion without any line numbers, but leave | |
232 | // space for them. This helps with lining up with previous | |
233 | // snippets from the actual error being reported. | |
c1a9b12d | 234 | let mut lines = complete.lines(); |
7453a54e | 235 | for line in lines.by_ref().take(MAX_HIGHLIGHT_LINES) { |
54a0048b SL |
236 | write!(&mut self.dst, "{0}:{1:2$} {3}\n", |
237 | fm.name, "", max_digits, line)?; | |
c1a9b12d SL |
238 | } |
239 | ||
240 | // if we elided some lines, add an ellipsis | |
7453a54e | 241 | if let Some(_) = lines.next() { |
54a0048b SL |
242 | write!(&mut self.dst, "{0:1$} {0:2$} ...\n", |
243 | "", fm.name.len(), max_digits)?; | |
c1a9b12d SL |
244 | } |
245 | ||
246 | Ok(()) | |
247 | } | |
248 | ||
249 | fn highlight_lines(&mut self, | |
7453a54e SL |
250 | msp: &MultiSpan, |
251 | lvl: Level) | |
c1a9b12d SL |
252 | -> io::Result<()> |
253 | { | |
7453a54e | 254 | let lines = match self.cm.span_to_lines(msp.to_span_bounds()) { |
c1a9b12d SL |
255 | Ok(lines) => lines, |
256 | Err(_) => { | |
54a0048b | 257 | write!(&mut self.dst, "(internal compiler error: unprintable span)\n")?; |
c1a9b12d SL |
258 | return Ok(()); |
259 | } | |
260 | }; | |
261 | ||
262 | let fm = &*lines.file; | |
7453a54e SL |
263 | if let None = fm.src { |
264 | return Ok(()); | |
265 | } | |
c1a9b12d | 266 | |
7453a54e SL |
267 | let display_line_infos = &lines.lines[..]; |
268 | assert!(display_line_infos.len() > 0); | |
c1a9b12d SL |
269 | |
270 | // Calculate the widest number to format evenly and fix #11715 | |
7453a54e SL |
271 | let digits = line_num_max_digits(display_line_infos.last().unwrap()); |
272 | let first_line_index = display_line_infos.first().unwrap().line_index; | |
c1a9b12d | 273 | |
7453a54e | 274 | let skip = fm.name.chars().count() + digits + 2; |
c1a9b12d | 275 | |
7453a54e SL |
276 | let mut spans = msp.spans.iter().peekable(); |
277 | let mut lines = display_line_infos.iter(); | |
278 | let mut prev_line_index = first_line_index.wrapping_sub(1); | |
c1a9b12d | 279 | |
7453a54e SL |
280 | // Display at most MAX_HIGHLIGHT_LINES lines. |
281 | let mut remaining_err_lines = MAX_HIGHLIGHT_LINES; | |
c1a9b12d | 282 | |
7453a54e SL |
283 | // To emit a overflowed spans code-lines *AFTER* the rendered spans |
284 | let mut overflowed_buf = String::new(); | |
285 | let mut overflowed = false; | |
c1a9b12d | 286 | |
7453a54e SL |
287 | // FIXME (#8706) |
288 | 'l: loop { | |
289 | if remaining_err_lines <= 0 { | |
290 | break; | |
c1a9b12d | 291 | } |
7453a54e SL |
292 | let line = match lines.next() { |
293 | Some(l) => l, | |
294 | None => break, | |
295 | }; | |
296 | ||
297 | // Skip is the number of characters we need to skip because they are | |
298 | // part of the 'filename:line ' part of the code line. | |
299 | let mut s: String = ::std::iter::repeat(' ').take(skip).collect(); | |
300 | let mut col = skip; | |
301 | let mut lastc = ' '; | |
302 | ||
303 | let cur_line_str = fm.get_line(line.line_index).unwrap(); | |
304 | let mut line_chars = cur_line_str.chars().enumerate().peekable(); | |
305 | let mut line_spans = 0; | |
306 | ||
307 | // Assemble spans for this line | |
308 | loop { | |
309 | // Peek here to preserve the span if it doesn't belong to this line | |
310 | let sp = match spans.peek() { | |
311 | Some(sp) => **sp, | |
312 | None => break, | |
313 | }; | |
314 | let lo = self.cm.lookup_char_pos(sp.lo); | |
315 | let hi = self.cm.lookup_char_pos(sp.hi); | |
316 | let line_num = line.line_index + 1; | |
317 | ||
318 | if !(lo.line <= line_num && hi.line >= line_num) { | |
319 | // This line is not contained in the span | |
320 | if overflowed { | |
321 | // Never elide the final line of an overflowed span | |
322 | prev_line_index = line.line_index - 1; | |
323 | overflowed = false; | |
324 | break; | |
325 | } | |
326 | ||
327 | if line_spans == 0 { | |
328 | continue 'l; | |
329 | } else { | |
330 | // This line is finished, now render the spans we've assembled | |
331 | break; | |
332 | } | |
333 | } | |
334 | spans.next(); | |
335 | line_spans += 1; | |
336 | ||
337 | if lo.line != hi.line { | |
338 | // Assemble extra code lines to be emitted after this lines spans | |
339 | // (substract `2` because the first and last line are rendered normally) | |
340 | let max_lines = cmp::min(remaining_err_lines, MAX_SP_LINES) - 2; | |
341 | prev_line_index = line.line_index; | |
342 | let count = cmp::min((hi.line - lo.line - 1), max_lines); | |
343 | for _ in 0..count { | |
344 | let line = match lines.next() { | |
345 | Some(l) => l, | |
346 | None => break, | |
347 | }; | |
348 | let line_str = fm.get_line(line.line_index).unwrap(); | |
349 | overflowed_buf.push_str(&format!("{}:{:>width$} {}\n", | |
350 | fm.name, | |
351 | line.line_index + 1, | |
352 | line_str, | |
353 | width=digits)); | |
354 | remaining_err_lines -= 1; | |
355 | prev_line_index += 1 | |
356 | } | |
357 | // Remember that the span overflowed to ensure | |
358 | // that we emit its last line exactly once | |
359 | // (other spans may, or may not, start on it) | |
360 | overflowed = true; | |
361 | break; | |
362 | } | |
363 | ||
364 | for (pos, ch) in line_chars.by_ref() { | |
c1a9b12d SL |
365 | lastc = ch; |
366 | if pos >= lo.col.to_usize() { break; } | |
7453a54e | 367 | // Whenever a tab occurs on the code line, we insert one on |
c1a9b12d SL |
368 | // the error-point-squiggly-line as well (instead of a space). |
369 | // That way the squiggly line will usually appear in the correct | |
370 | // position. | |
371 | match ch { | |
372 | '\t' => { | |
373 | col += 8 - col%8; | |
374 | s.push('\t'); | |
375 | }, | |
376 | _ => { | |
377 | col += 1; | |
378 | s.push(' '); | |
379 | }, | |
380 | } | |
381 | } | |
382 | ||
7453a54e SL |
383 | s.push('^'); |
384 | let col_ptr = col; | |
c1a9b12d SL |
385 | let count = match lastc { |
386 | // Most terminals have a tab stop every eight columns by default | |
387 | '\t' => 8 - col%8, | |
388 | _ => 1, | |
389 | }; | |
390 | col += count; | |
391 | s.extend(::std::iter::repeat('~').take(count)); | |
392 | ||
9cc50fc6 | 393 | let hi = self.cm.lookup_char_pos(sp.hi); |
c1a9b12d | 394 | if hi.col != lo.col { |
7453a54e SL |
395 | let mut chars = line_chars.by_ref(); |
396 | loop { | |
397 | // We peek here to preserve the value for the next span | |
398 | let (pos, ch) = match chars.peek() { | |
399 | Some(elem) => *elem, | |
400 | None => break, | |
401 | }; | |
c1a9b12d SL |
402 | if pos >= hi.col.to_usize() { break; } |
403 | let count = match ch { | |
404 | '\t' => 8 - col%8, | |
405 | _ => 1, | |
406 | }; | |
407 | col += count; | |
408 | s.extend(::std::iter::repeat('~').take(count)); | |
7453a54e SL |
409 | |
410 | chars.next(); | |
c1a9b12d SL |
411 | } |
412 | } | |
7453a54e | 413 | if (col - col_ptr) > 0 { |
c1a9b12d SL |
414 | // One extra squiggly is replaced by a "^" |
415 | s.pop(); | |
416 | } | |
7453a54e | 417 | } |
c1a9b12d | 418 | |
7453a54e SL |
419 | // If we elided something put an ellipsis. |
420 | if prev_line_index != line.line_index.wrapping_sub(1) && !overflowed { | |
54a0048b | 421 | write!(&mut self.dst, "{0:1$}...\n", "", skip)?; |
7453a54e SL |
422 | } |
423 | ||
424 | // Print offending code-line | |
425 | remaining_err_lines -= 1; | |
54a0048b SL |
426 | write!(&mut self.dst, "{}:{:>width$} {}\n", |
427 | fm.name, | |
428 | line.line_index + 1, | |
429 | cur_line_str, | |
430 | width=digits)?; | |
7453a54e SL |
431 | |
432 | if s.len() > skip { | |
433 | // Render the spans we assembled previously (if any). | |
54a0048b SL |
434 | println_maybe_styled!(&mut self.dst, term::Attr::ForegroundColor(lvl.color()), |
435 | "{}", s)?; | |
c1a9b12d | 436 | } |
7453a54e SL |
437 | |
438 | if !overflowed_buf.is_empty() { | |
439 | // Print code-lines trailing the rendered spans (when a span overflows) | |
54a0048b | 440 | write!(&mut self.dst, "{}", &overflowed_buf)?; |
7453a54e SL |
441 | overflowed_buf.clear(); |
442 | } else { | |
443 | prev_line_index = line.line_index; | |
444 | } | |
445 | } | |
446 | ||
447 | // If we elided something, put an ellipsis. | |
448 | if lines.next().is_some() { | |
54a0048b | 449 | write!(&mut self.dst, "{0:1$}...\n", "", skip)?; |
c1a9b12d SL |
450 | } |
451 | Ok(()) | |
452 | } | |
453 | ||
454 | /// Here are the differences between this and the normal `highlight_lines`: | |
7453a54e SL |
455 | /// `end_highlight_lines` will always put arrow on the last byte of each |
456 | /// span (instead of the first byte). Also, when a span is too long (more | |
c1a9b12d SL |
457 | /// than 6 lines), `end_highlight_lines` will print the first line, then |
458 | /// dot dot dot, then last line, whereas `highlight_lines` prints the first | |
459 | /// six lines. | |
460 | #[allow(deprecated)] | |
461 | fn end_highlight_lines(&mut self, | |
7453a54e SL |
462 | msp: &MultiSpan, |
463 | lvl: Level) | |
c1a9b12d | 464 | -> io::Result<()> { |
7453a54e | 465 | let lines = match self.cm.span_to_lines(msp.to_span_bounds()) { |
c1a9b12d SL |
466 | Ok(lines) => lines, |
467 | Err(_) => { | |
54a0048b | 468 | write!(&mut self.dst, "(internal compiler error: unprintable span)\n")?; |
c1a9b12d SL |
469 | return Ok(()); |
470 | } | |
471 | }; | |
472 | ||
473 | let fm = &*lines.file; | |
7453a54e SL |
474 | if let None = fm.src { |
475 | return Ok(()); | |
476 | } | |
c1a9b12d SL |
477 | |
478 | let lines = &lines.lines[..]; | |
7453a54e SL |
479 | |
480 | // Calculate the widest number to format evenly | |
481 | let first_line = lines.first().unwrap(); | |
482 | let last_line = lines.last().unwrap(); | |
483 | let digits = line_num_max_digits(last_line); | |
484 | ||
485 | let skip = fm.name.chars().count() + digits + 2; | |
486 | ||
487 | let mut spans = msp.spans.iter().peekable(); | |
488 | let mut lines = lines.iter(); | |
489 | let mut prev_line_index = first_line.line_index.wrapping_sub(1); | |
490 | ||
491 | // Display at most MAX_HIGHLIGHT_LINES lines. | |
492 | let mut remaining_err_lines = MAX_HIGHLIGHT_LINES; | |
493 | ||
494 | 'l: loop { | |
495 | if remaining_err_lines <= 0 { | |
496 | break; | |
c1a9b12d | 497 | } |
7453a54e SL |
498 | let line = match lines.next() { |
499 | Some(line) => line, | |
500 | None => break, | |
501 | }; | |
502 | ||
503 | // Skip is the number of characters we need to skip because they are | |
504 | // part of the 'filename:line ' part of the previous line. | |
505 | let mut s: String = ::std::iter::repeat(' ').take(skip).collect(); | |
506 | ||
507 | let line_str = fm.get_line(line.line_index).unwrap(); | |
508 | let mut line_chars = line_str.chars().enumerate(); | |
509 | let mut line_spans = 0; | |
510 | ||
511 | loop { | |
512 | // Peek here to preserve the span if it doesn't belong to this line | |
513 | let sp = match spans.peek() { | |
514 | Some(sp) => **sp, | |
515 | None => break, | |
516 | }; | |
517 | let lo = self.cm.lookup_char_pos(sp.lo); | |
518 | let hi = self.cm.lookup_char_pos(sp.hi); | |
519 | let elide_sp = (hi.line - lo.line) >= MAX_SP_LINES; | |
520 | ||
521 | let line_num = line.line_index + 1; | |
522 | if !(lo.line <= line_num && hi.line >= line_num) { | |
523 | // This line is not contained in the span | |
524 | if line_spans == 0 { | |
525 | continue 'l; | |
526 | } else { | |
527 | // This line is finished, now render the spans we've assembled | |
528 | break | |
529 | } | |
530 | } else if hi.line > line_num { | |
531 | if elide_sp && lo.line < line_num { | |
532 | // This line is inbetween the first and last line of the span, | |
533 | // so we may want to elide it. | |
534 | continue 'l; | |
535 | } else { | |
536 | break | |
537 | } | |
c1a9b12d | 538 | } |
7453a54e SL |
539 | line_spans += 1; |
540 | spans.next(); | |
541 | ||
542 | for (pos, ch) in line_chars.by_ref() { | |
543 | // Span seems to use half-opened interval, so subtract 1 | |
544 | if pos >= hi.col.to_usize() - 1 { break; } | |
545 | // Whenever a tab occurs on the previous line, we insert one on | |
546 | // the error-point-squiggly-line as well (instead of a space). | |
547 | // That way the squiggly line will usually appear in the correct | |
548 | // position. | |
549 | match ch { | |
550 | '\t' => s.push('\t'), | |
551 | _ => s.push(' '), | |
552 | } | |
c1a9b12d | 553 | } |
7453a54e SL |
554 | s.push('^'); |
555 | } | |
556 | ||
557 | if prev_line_index != line.line_index.wrapping_sub(1) { | |
558 | // If we elided something, put an ellipsis. | |
54a0048b | 559 | write!(&mut self.dst, "{0:1$}...\n", "", skip)?; |
c1a9b12d | 560 | } |
7453a54e SL |
561 | |
562 | // Print offending code-lines | |
54a0048b SL |
563 | write!(&mut self.dst, "{}:{:>width$} {}\n", fm.name, |
564 | line.line_index + 1, line_str, width=digits)?; | |
7453a54e SL |
565 | remaining_err_lines -= 1; |
566 | ||
567 | if s.len() > skip { | |
568 | // Render the spans we assembled previously (if any) | |
54a0048b SL |
569 | println_maybe_styled!(&mut self.dst, term::Attr::ForegroundColor(lvl.color()), |
570 | "{}", s)?; | |
7453a54e SL |
571 | } |
572 | prev_line_index = line.line_index; | |
c1a9b12d | 573 | } |
7453a54e | 574 | Ok(()) |
c1a9b12d SL |
575 | } |
576 | ||
577 | fn print_macro_backtrace(&mut self, | |
c1a9b12d SL |
578 | sp: Span) |
579 | -> io::Result<()> { | |
e9174d1e | 580 | let mut last_span = codemap::DUMMY_SP; |
9cc50fc6 SL |
581 | let mut span = sp; |
582 | ||
583 | loop { | |
584 | let span_name_span = self.cm.with_expn_info(span.expn_id, |expn_info| { | |
585 | expn_info.map(|ei| { | |
586 | let (pre, post) = match ei.callee.format { | |
587 | codemap::MacroAttribute(..) => ("#[", "]"), | |
588 | codemap::MacroBang(..) => ("", "!"), | |
589 | }; | |
590 | let macro_decl_name = format!("in this expansion of {}{}{}", | |
591 | pre, | |
592 | ei.callee.name(), | |
593 | post); | |
594 | let def_site_span = ei.callee.span; | |
595 | (ei.call_site, macro_decl_name, def_site_span) | |
596 | }) | |
597 | }); | |
598 | let (macro_decl_name, def_site_span) = match span_name_span { | |
599 | None => break, | |
600 | Some((sp, macro_decl_name, def_site_span)) => { | |
601 | span = sp; | |
602 | (macro_decl_name, def_site_span) | |
c1a9b12d | 603 | } |
9cc50fc6 SL |
604 | }; |
605 | ||
606 | // Don't print recursive invocations | |
7453a54e | 607 | if !span.source_equal(&last_span) { |
9cc50fc6 SL |
608 | let mut diag_string = macro_decl_name; |
609 | if let Some(def_site_span) = def_site_span { | |
610 | diag_string.push_str(&format!(" (defined in {})", | |
611 | self.cm.span_to_filename(def_site_span))); | |
612 | } | |
613 | ||
614 | let snippet = self.cm.span_to_string(span); | |
54a0048b | 615 | print_diagnostic(&mut self.dst, &snippet, Note, &diag_string, None)?; |
9cc50fc6 SL |
616 | } |
617 | last_span = span; | |
c1a9b12d | 618 | } |
e9174d1e SL |
619 | |
620 | Ok(()) | |
c1a9b12d | 621 | } |
1a4d82fc | 622 | } |
970d7e83 | 623 | |
7453a54e SL |
624 | fn line_num_max_digits(line: &codemap::LineInfo) -> usize { |
625 | let mut max_line_num = line.line_index + 1; | |
626 | let mut digits = 0; | |
627 | while max_line_num > 0 { | |
628 | max_line_num /= 10; | |
629 | digits += 1; | |
630 | } | |
631 | digits | |
632 | } | |
633 | ||
9cc50fc6 SL |
634 | fn print_diagnostic(dst: &mut Destination, |
635 | topic: &str, | |
636 | lvl: Level, | |
637 | msg: &str, | |
638 | code: Option<&str>) | |
639 | -> io::Result<()> { | |
640 | if !topic.is_empty() { | |
54a0048b | 641 | write!(dst, "{} ", topic)?; |
9cc50fc6 SL |
642 | } |
643 | ||
54a0048b SL |
644 | print_maybe_styled!(dst, term::Attr::ForegroundColor(lvl.color()), |
645 | "{}: ", lvl.to_string())?; | |
646 | print_maybe_styled!(dst, term::Attr::Bold, "{}", msg)?; | |
9cc50fc6 | 647 | |
7453a54e SL |
648 | if let Some(code) = code { |
649 | let style = term::Attr::ForegroundColor(term::color::BRIGHT_MAGENTA); | |
54a0048b | 650 | print_maybe_styled!(dst, style, " [{}]", code.clone())?; |
9cc50fc6 | 651 | } |
54a0048b | 652 | write!(dst, "\n")?; |
9cc50fc6 SL |
653 | Ok(()) |
654 | } | |
655 | ||
c34b1796 AL |
656 | #[cfg(unix)] |
657 | fn stderr_isatty() -> bool { | |
92a42be0 | 658 | use libc; |
c34b1796 AL |
659 | unsafe { libc::isatty(libc::STDERR_FILENO) != 0 } |
660 | } | |
661 | #[cfg(windows)] | |
662 | fn stderr_isatty() -> bool { | |
92a42be0 SL |
663 | type DWORD = u32; |
664 | type BOOL = i32; | |
665 | type HANDLE = *mut u8; | |
666 | const STD_ERROR_HANDLE: DWORD = -12i32 as DWORD; | |
c34b1796 | 667 | extern "system" { |
92a42be0 SL |
668 | fn GetStdHandle(which: DWORD) -> HANDLE; |
669 | fn GetConsoleMode(hConsoleHandle: HANDLE, | |
670 | lpMode: *mut DWORD) -> BOOL; | |
c34b1796 AL |
671 | } |
672 | unsafe { | |
673 | let handle = GetStdHandle(STD_ERROR_HANDLE); | |
674 | let mut out = 0; | |
675 | GetConsoleMode(handle, &mut out) != 0 | |
676 | } | |
677 | } | |
678 | ||
9cc50fc6 SL |
679 | enum Destination { |
680 | Terminal(Box<term::StderrTerminal>), | |
681 | Raw(Box<Write + Send>), | |
682 | } | |
683 | ||
684 | impl Destination { | |
685 | fn from_stderr() -> Destination { | |
686 | match term::stderr() { | |
687 | Some(t) => Terminal(t), | |
688 | None => Raw(Box::new(io::stderr())), | |
689 | } | |
690 | } | |
691 | ||
692 | fn print_maybe_styled(&mut self, | |
693 | args: fmt::Arguments, | |
694 | color: term::Attr, | |
695 | print_newline_at_end: bool) | |
696 | -> io::Result<()> { | |
697 | match *self { | |
698 | Terminal(ref mut t) => { | |
54a0048b | 699 | t.attr(color)?; |
9cc50fc6 SL |
700 | // If `msg` ends in a newline, we need to reset the color before |
701 | // the newline. We're making the assumption that we end up writing | |
702 | // to a `LineBufferedWriter`, which means that emitting the reset | |
703 | // after the newline ends up buffering the reset until we print | |
704 | // another line or exit. Buffering the reset is a problem if we're | |
705 | // sharing the terminal with any other programs (e.g. other rustc | |
706 | // instances via `make -jN`). | |
707 | // | |
708 | // Note that if `msg` contains any internal newlines, this will | |
709 | // result in the `LineBufferedWriter` flushing twice instead of | |
710 | // once, which still leaves the opportunity for interleaved output | |
711 | // to be miscolored. We assume this is rare enough that we don't | |
712 | // have to worry about it. | |
54a0048b SL |
713 | t.write_fmt(args)?; |
714 | t.reset()?; | |
9cc50fc6 SL |
715 | if print_newline_at_end { |
716 | t.write_all(b"\n") | |
717 | } else { | |
718 | Ok(()) | |
719 | } | |
720 | } | |
721 | Raw(ref mut w) => { | |
54a0048b | 722 | w.write_fmt(args)?; |
9cc50fc6 SL |
723 | if print_newline_at_end { |
724 | w.write_all(b"\n") | |
725 | } else { | |
726 | Ok(()) | |
727 | } | |
728 | } | |
729 | } | |
730 | } | |
731 | } | |
732 | ||
c34b1796 AL |
733 | impl Write for Destination { |
734 | fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { | |
735 | match *self { | |
736 | Terminal(ref mut t) => t.write(bytes), | |
737 | Raw(ref mut w) => w.write(bytes), | |
738 | } | |
739 | } | |
740 | fn flush(&mut self) -> io::Result<()> { | |
1a4d82fc | 741 | match *self { |
c34b1796 AL |
742 | Terminal(ref mut t) => t.flush(), |
743 | Raw(ref mut w) => w.flush(), | |
1a4d82fc JJ |
744 | } |
745 | } | |
223e47cc LB |
746 | } |
747 | ||
c1a9b12d SL |
748 | |
749 | #[cfg(test)] | |
750 | mod test { | |
7453a54e | 751 | use errors::{Level, CodeSuggestion}; |
9cc50fc6 | 752 | use super::EmitterWriter; |
7453a54e | 753 | use codemap::{mk_sp, CodeMap, Span, MultiSpan, BytePos, NO_EXPANSION}; |
c1a9b12d SL |
754 | use std::sync::{Arc, Mutex}; |
755 | use std::io::{self, Write}; | |
756 | use std::str::from_utf8; | |
9cc50fc6 | 757 | use std::rc::Rc; |
c1a9b12d | 758 | |
7453a54e SL |
759 | struct Sink(Arc<Mutex<Vec<u8>>>); |
760 | impl Write for Sink { | |
761 | fn write(&mut self, data: &[u8]) -> io::Result<usize> { | |
762 | Write::write(&mut *self.0.lock().unwrap(), data) | |
763 | } | |
764 | fn flush(&mut self) -> io::Result<()> { Ok(()) } | |
765 | } | |
766 | ||
767 | /// Given a string like " ^~~~~~~~~~~~ ", produces a span | |
768 | /// coverting that range. The idea is that the string has the same | |
769 | /// length as the input, and we uncover the byte positions. Note | |
770 | /// that this can span lines and so on. | |
771 | fn span_from_selection(input: &str, selection: &str) -> Span { | |
772 | assert_eq!(input.len(), selection.len()); | |
773 | let left_index = selection.find('^').unwrap() as u32; | |
774 | let right_index = selection.rfind('~').map(|x|x as u32).unwrap_or(left_index); | |
775 | Span { lo: BytePos(left_index), hi: BytePos(right_index + 1), expn_id: NO_EXPANSION } | |
776 | } | |
777 | ||
c1a9b12d SL |
778 | // Diagnostic doesn't align properly in span where line number increases by one digit |
779 | #[test] | |
780 | fn test_hilight_suggestion_issue_11715() { | |
c1a9b12d | 781 | let data = Arc::new(Mutex::new(Vec::new())); |
9cc50fc6 SL |
782 | let cm = Rc::new(CodeMap::new()); |
783 | let mut ew = EmitterWriter::new(Box::new(Sink(data.clone())), None, cm.clone()); | |
c1a9b12d SL |
784 | let content = "abcdefg |
785 | koksi | |
786 | line3 | |
787 | line4 | |
788 | cinq | |
789 | line6 | |
790 | line7 | |
791 | line8 | |
792 | line9 | |
793 | line10 | |
794 | e-lä-vän | |
795 | tolv | |
796 | dreizehn | |
797 | "; | |
798 | let file = cm.new_filemap_and_lines("dummy.txt", content); | |
799 | let start = file.lines.borrow()[7]; | |
800 | let end = file.lines.borrow()[11]; | |
801 | let sp = mk_sp(start, end); | |
802 | let lvl = Level::Error; | |
c1a9b12d | 803 | println!("highlight_lines"); |
7453a54e | 804 | ew.highlight_lines(&sp.into(), lvl).unwrap(); |
c1a9b12d SL |
805 | println!("done"); |
806 | let vec = data.lock().unwrap().clone(); | |
807 | let vec: &[u8] = &vec; | |
808 | let str = from_utf8(vec).unwrap(); | |
809 | println!("{}", str); | |
810 | assert_eq!(str, "dummy.txt: 8 line8\n\ | |
811 | dummy.txt: 9 line9\n\ | |
812 | dummy.txt:10 line10\n\ | |
813 | dummy.txt:11 e-lä-vän\n\ | |
814 | dummy.txt:12 tolv\n"); | |
815 | } | |
7453a54e SL |
816 | |
817 | #[test] | |
818 | fn test_single_span_splice() { | |
819 | // Test that a `MultiSpan` containing a single span splices a substition correctly | |
820 | let cm = CodeMap::new(); | |
821 | let inputtext = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n"; | |
822 | let selection = " \n ^~\n~~~\n~~~~~ \n \n"; | |
823 | cm.new_filemap_and_lines("blork.rs", inputtext); | |
824 | let sp = span_from_selection(inputtext, selection); | |
825 | let msp: MultiSpan = sp.into(); | |
826 | ||
827 | // check that we are extracting the text we thought we were extracting | |
828 | assert_eq!(&cm.span_to_snippet(sp).unwrap(), "BB\nCCC\nDDDDD"); | |
829 | ||
830 | let substitute = "ZZZZZZ".to_owned(); | |
831 | let expected = "bbbbZZZZZZddddd"; | |
832 | let suggest = CodeSuggestion { | |
833 | msp: msp, | |
834 | substitutes: vec![substitute], | |
835 | }; | |
836 | assert_eq!(suggest.splice_lines(&cm), expected); | |
837 | } | |
838 | ||
839 | #[test] | |
840 | fn test_multiple_span_splice() { | |
841 | // Test that a `MultiSpan` containing multiple spans splices substitions on | |
842 | // several lines correctly | |
843 | let cm = CodeMap::new(); | |
844 | let inp = "aaaaabbbbBB\nZZ\nZZ\nCCCDDDDDdddddeee"; | |
845 | let sp1 = " ^~~~~~\n \n \n "; | |
846 | let sp2 = " \n \n \n^~~~~~ "; | |
847 | let sp3 = " \n \n \n ^~~ "; | |
848 | let sp4 = " \n \n \n ^~~~ "; | |
849 | ||
850 | let span_eq = |sp, eq| assert_eq!(&cm.span_to_snippet(sp).unwrap(), eq); | |
851 | ||
852 | cm.new_filemap_and_lines("blork.rs", inp); | |
853 | let sp1 = span_from_selection(inp, sp1); | |
854 | let sp2 = span_from_selection(inp, sp2); | |
855 | let sp3 = span_from_selection(inp, sp3); | |
856 | let sp4 = span_from_selection(inp, sp4); | |
857 | span_eq(sp1, "bbbbBB"); | |
858 | span_eq(sp2, "CCCDDD"); | |
859 | span_eq(sp3, "ddd"); | |
860 | span_eq(sp4, "ddee"); | |
861 | ||
862 | let substitutes: Vec<String> = ["1", "2", "3", "4"].iter().map(|x|x.to_string()).collect(); | |
863 | let expected = "aaaaa1\nZZ\nZZ\n2DD34e"; | |
864 | ||
865 | let test = |msp| { | |
866 | let suggest = CodeSuggestion { | |
867 | msp: msp, | |
868 | substitutes: substitutes.clone(), | |
869 | }; | |
870 | let actual = suggest.splice_lines(&cm); | |
871 | assert_eq!(actual, expected); | |
872 | }; | |
873 | test(MultiSpan { spans: vec![sp1, sp2, sp3, sp4] }); | |
874 | ||
875 | // Test ordering and merging by `MultiSpan::push` | |
876 | let mut msp = MultiSpan::new(); | |
877 | msp.push_merge(sp2); | |
878 | msp.push_merge(sp1); | |
879 | assert_eq!(&msp.spans, &[sp1, sp2]); | |
880 | msp.push_merge(sp4); | |
881 | assert_eq!(&msp.spans, &[sp1, sp2, sp4]); | |
882 | msp.push_merge(sp3); | |
883 | assert_eq!(&msp.spans, &[sp1, sp2, sp3, sp4]); | |
884 | test(msp); | |
885 | } | |
886 | ||
887 | #[test] | |
888 | fn test_multispan_highlight() { | |
889 | let data = Arc::new(Mutex::new(Vec::new())); | |
890 | let cm = Rc::new(CodeMap::new()); | |
891 | let mut diag = EmitterWriter::new(Box::new(Sink(data.clone())), None, cm.clone()); | |
892 | ||
893 | let inp = "_____aaaaaa____bbbbbb__cccccdd_"; | |
894 | let sp1 = " ^~~~~~ "; | |
895 | let sp2 = " ^~~~~~ "; | |
896 | let sp3 = " ^~~~~ "; | |
897 | let sp4 = " ^~~~ "; | |
898 | let sp34 = " ^~~~~~~ "; | |
899 | let sp4_end = " ^~ "; | |
900 | ||
901 | let expect_start = "dummy.txt:1 _____aaaaaa____bbbbbb__cccccdd_\n\ | |
902 | \x20 ^~~~~~ ^~~~~~ ^~~~~~~\n"; | |
903 | let expect_end = "dummy.txt:1 _____aaaaaa____bbbbbb__cccccdd_\n\ | |
904 | \x20 ^ ^ ^ ^\n"; | |
905 | ||
906 | let span = |sp, expected| { | |
907 | let sp = span_from_selection(inp, sp); | |
908 | assert_eq!(&cm.span_to_snippet(sp).unwrap(), expected); | |
909 | sp | |
910 | }; | |
911 | cm.new_filemap_and_lines("dummy.txt", inp); | |
912 | let sp1 = span(sp1, "aaaaaa"); | |
913 | let sp2 = span(sp2, "bbbbbb"); | |
914 | let sp3 = span(sp3, "ccccc"); | |
915 | let sp4 = span(sp4, "ccdd"); | |
916 | let sp34 = span(sp34, "cccccdd"); | |
917 | let sp4_end = span(sp4_end, "dd"); | |
918 | ||
919 | let spans = vec![sp1, sp2, sp3, sp4]; | |
920 | ||
921 | let test = |expected, highlight: &mut FnMut()| { | |
922 | data.lock().unwrap().clear(); | |
923 | highlight(); | |
924 | let vec = data.lock().unwrap().clone(); | |
925 | let actual = from_utf8(&vec[..]).unwrap(); | |
926 | assert_eq!(actual, expected); | |
927 | }; | |
928 | ||
929 | let msp = MultiSpan { spans: vec![sp1, sp2, sp34] }; | |
930 | let msp_end = MultiSpan { spans: vec![sp1, sp2, sp3, sp4_end] }; | |
931 | test(expect_start, &mut || { | |
932 | diag.highlight_lines(&msp, Level::Error).unwrap(); | |
933 | }); | |
934 | test(expect_end, &mut || { | |
935 | diag.end_highlight_lines(&msp_end, Level::Error).unwrap(); | |
936 | }); | |
937 | test(expect_start, &mut || { | |
938 | for msp in cm.group_spans(spans.clone()) { | |
939 | diag.highlight_lines(&msp, Level::Error).unwrap(); | |
940 | } | |
941 | }); | |
942 | test(expect_end, &mut || { | |
943 | for msp in cm.end_group_spans(spans.clone()) { | |
944 | diag.end_highlight_lines(&msp, Level::Error).unwrap(); | |
945 | } | |
946 | }); | |
947 | } | |
948 | ||
949 | #[test] | |
950 | fn test_huge_multispan_highlight() { | |
951 | let data = Arc::new(Mutex::new(Vec::new())); | |
952 | let cm = Rc::new(CodeMap::new()); | |
953 | let mut diag = EmitterWriter::new(Box::new(Sink(data.clone())), None, cm.clone()); | |
954 | ||
955 | let inp = "aaaaa\n\ | |
956 | aaaaa\n\ | |
957 | aaaaa\n\ | |
958 | bbbbb\n\ | |
959 | ccccc\n\ | |
960 | xxxxx\n\ | |
961 | yyyyy\n\ | |
962 | _____\n\ | |
963 | ddd__eee_\n\ | |
964 | elided\n\ | |
965 | __f_gg"; | |
966 | let file = cm.new_filemap_and_lines("dummy.txt", inp); | |
967 | ||
968 | let span = |lo, hi, (off_lo, off_hi)| { | |
969 | let lines = file.lines.borrow(); | |
970 | let (mut lo, mut hi): (BytePos, BytePos) = (lines[lo], lines[hi]); | |
971 | lo.0 += off_lo; | |
972 | hi.0 += off_hi; | |
973 | mk_sp(lo, hi) | |
974 | }; | |
975 | let sp0 = span(4, 6, (0, 5)); | |
976 | let sp1 = span(0, 6, (0, 5)); | |
977 | let sp2 = span(8, 8, (0, 3)); | |
978 | let sp3 = span(8, 8, (5, 8)); | |
979 | let sp4 = span(10, 10, (2, 3)); | |
980 | let sp5 = span(10, 10, (4, 6)); | |
981 | ||
982 | let expect0 = "dummy.txt: 5 ccccc\n\ | |
983 | dummy.txt: 6 xxxxx\n\ | |
984 | dummy.txt: 7 yyyyy\n\ | |
985 | \x20 ...\n\ | |
986 | dummy.txt: 9 ddd__eee_\n\ | |
987 | \x20 ^~~ ^~~\n\ | |
988 | \x20 ...\n\ | |
989 | dummy.txt:11 __f_gg\n\ | |
990 | \x20 ^ ^~\n"; | |
991 | ||
992 | let expect = "dummy.txt: 1 aaaaa\n\ | |
993 | dummy.txt: 2 aaaaa\n\ | |
994 | dummy.txt: 3 aaaaa\n\ | |
995 | dummy.txt: 4 bbbbb\n\ | |
996 | dummy.txt: 5 ccccc\n\ | |
997 | dummy.txt: 6 xxxxx\n\ | |
998 | \x20 ...\n"; | |
999 | ||
1000 | let expect_g1 = "dummy.txt:1 aaaaa\n\ | |
1001 | dummy.txt:2 aaaaa\n\ | |
1002 | dummy.txt:3 aaaaa\n\ | |
1003 | dummy.txt:4 bbbbb\n\ | |
1004 | dummy.txt:5 ccccc\n\ | |
1005 | dummy.txt:6 xxxxx\n\ | |
1006 | \x20 ...\n"; | |
1007 | ||
1008 | let expect2 = "dummy.txt: 9 ddd__eee_\n\ | |
1009 | \x20 ^~~ ^~~\n\ | |
1010 | \x20 ...\n\ | |
1011 | dummy.txt:11 __f_gg\n\ | |
1012 | \x20 ^ ^~\n"; | |
1013 | ||
1014 | ||
1015 | let expect_end = "dummy.txt: 1 aaaaa\n\ | |
1016 | \x20 ...\n\ | |
1017 | dummy.txt: 7 yyyyy\n\ | |
1018 | \x20 ^\n\ | |
1019 | \x20 ...\n\ | |
1020 | dummy.txt: 9 ddd__eee_\n\ | |
1021 | \x20 ^ ^\n\ | |
1022 | \x20 ...\n\ | |
1023 | dummy.txt:11 __f_gg\n\ | |
1024 | \x20 ^ ^\n"; | |
1025 | ||
1026 | let expect0_end = "dummy.txt: 5 ccccc\n\ | |
1027 | dummy.txt: 6 xxxxx\n\ | |
1028 | dummy.txt: 7 yyyyy\n\ | |
1029 | \x20 ^\n\ | |
1030 | \x20 ...\n\ | |
1031 | dummy.txt: 9 ddd__eee_\n\ | |
1032 | \x20 ^ ^\n\ | |
1033 | \x20 ...\n\ | |
1034 | dummy.txt:11 __f_gg\n\ | |
1035 | \x20 ^ ^\n"; | |
1036 | ||
1037 | let expect_end_g1 = "dummy.txt:1 aaaaa\n\ | |
1038 | \x20 ...\n\ | |
1039 | dummy.txt:7 yyyyy\n\ | |
1040 | \x20 ^\n"; | |
1041 | ||
1042 | let expect2_end = "dummy.txt: 9 ddd__eee_\n\ | |
1043 | \x20 ^ ^\n\ | |
1044 | \x20 ...\n\ | |
1045 | dummy.txt:11 __f_gg\n\ | |
1046 | \x20 ^ ^\n"; | |
1047 | ||
1048 | let expect_groups = [expect2, expect_g1]; | |
1049 | let expect_end_groups = [expect2_end, expect_end_g1]; | |
1050 | let spans = vec![sp3, sp1, sp4, sp2, sp5]; | |
1051 | ||
1052 | macro_rules! test { | |
1053 | ($expected: expr, $highlight: expr) => ({ | |
1054 | data.lock().unwrap().clear(); | |
1055 | $highlight(); | |
1056 | let vec = data.lock().unwrap().clone(); | |
1057 | let actual = from_utf8(&vec[..]).unwrap(); | |
1058 | println!("actual:"); | |
1059 | println!("{}", actual); | |
1060 | println!("expected:"); | |
1061 | println!("{}", $expected); | |
1062 | assert_eq!(&actual[..], &$expected[..]); | |
1063 | }); | |
1064 | } | |
1065 | ||
1066 | let msp0 = MultiSpan { spans: vec![sp0, sp2, sp3, sp4, sp5] }; | |
1067 | let msp = MultiSpan { spans: vec![sp1, sp2, sp3, sp4, sp5] }; | |
1068 | let msp2 = MultiSpan { spans: vec![sp2, sp3, sp4, sp5] }; | |
1069 | ||
1070 | test!(expect0, || { | |
1071 | diag.highlight_lines(&msp0, Level::Error).unwrap(); | |
1072 | }); | |
1073 | test!(expect0_end, || { | |
1074 | diag.end_highlight_lines(&msp0, Level::Error).unwrap(); | |
1075 | }); | |
1076 | test!(expect, || { | |
1077 | diag.highlight_lines(&msp, Level::Error).unwrap(); | |
1078 | }); | |
1079 | test!(expect_end, || { | |
1080 | diag.end_highlight_lines(&msp, Level::Error).unwrap(); | |
1081 | }); | |
1082 | test!(expect2, || { | |
1083 | diag.highlight_lines(&msp2, Level::Error).unwrap(); | |
1084 | }); | |
1085 | test!(expect2_end, || { | |
1086 | diag.end_highlight_lines(&msp2, Level::Error).unwrap(); | |
1087 | }); | |
1088 | for (msp, expect) in cm.group_spans(spans.clone()).iter().zip(expect_groups.iter()) { | |
1089 | test!(expect, || { | |
1090 | diag.highlight_lines(&msp, Level::Error).unwrap(); | |
1091 | }); | |
1092 | } | |
1093 | for (msp, expect) in cm.group_spans(spans.clone()).iter().zip(expect_end_groups.iter()) { | |
1094 | test!(expect, || { | |
1095 | diag.end_highlight_lines(&msp, Level::Error).unwrap(); | |
1096 | }); | |
1097 | } | |
1098 | } | |
c1a9b12d | 1099 | } |