diff --git a/src/libsyntax/errors/emitter.rs b/src/libsyntax/errors/emitter.rs index a5c8916636454..9111f2a01133d 100644 --- a/src/libsyntax/errors/emitter.rs +++ b/src/libsyntax/errors/emitter.rs @@ -426,11 +426,11 @@ impl Destination { } Style::Quotation => { } - Style::Underline => { + Style::UnderlinePrimary | Style::LabelPrimary => { self.start_attr(term::Attr::Bold)?; self.start_attr(term::Attr::ForegroundColor(lvl.color()))?; } - Style::Label => { + Style::UnderlineSecondary | Style::LabelSecondary => { self.start_attr(term::Attr::Bold)?; self.start_attr(term::Attr::ForegroundColor(term::color::BRIGHT_BLUE))?; } @@ -575,12 +575,12 @@ mod test { let str = from_utf8(vec).unwrap(); println!("r#\"\n{}\"#", str); assert_eq!(str, &r#" - --> dummy.txt:8:1: 12:1 + --> dummy.txt:8:1 8 |> line8 - |> ~~~~~~~~~~~~~ + |> ^^^^^^^^^^^^^ ... 11 |> e-lä-vän - |> ~~~~~~~~~~~~~~~~ + |> ^^^^^^^^^^^^^^^^ "#[1..]); } @@ -620,9 +620,9 @@ mod test { let sp34 = " ~~~~~~~ "; let expect_start = &r#" - --> dummy.txt:1:6: 1:31 + --> dummy.txt:1:6 1 |> _____aaaaaa____bbbbbb__cccccdd_ - |> ~~~~~~ ~~~~~~ ~~~~~~~ + |> ------ ------ ------- "#[1..]; let span = |sp, expected| { @@ -692,29 +692,29 @@ mod test { let sp5 = span(10, 10, (4, 6)); let expect0 = &r#" - --> dummy.txt:5:1: 11:7 + --> dummy.txt:5:1 5 |> ccccc - |> ~~~~~ + |> ----- ... 8 |> _____ 9 |> ddd__eee_ - |> ~~~ ~~~ + |> --- --- 10 |> elided 11 |> __f_gg - |> ~ ~~ + |> - -- "#[1..]; let expect = &r#" - --> dummy.txt:1:1: 11:7 + --> dummy.txt:1:1 1 |> aaaaa - |> ~~~~~ + |> ----- ... 8 |> _____ 9 |> ddd__eee_ - |> ~~~ ~~~ + |> --- --- 10 |> elided 11 |> __f_gg - |> ~ ~~ + |> - -- "#[1..]; macro_rules! test { diff --git a/src/libsyntax/errors/snippet/mod.rs b/src/libsyntax/errors/snippet/mod.rs index 8f63f6a05ba91..ebf1d4dd54724 100644 --- a/src/libsyntax/errors/snippet/mod.rs +++ b/src/libsyntax/errors/snippet/mod.rs @@ -11,7 +11,6 @@ // Code for annotating snippets. use codemap::{CharPos, CodeMap, FileMap, LineInfo, Span}; -use std::iter; use std::rc::Rc; use std::mem; use std::ops::Range; @@ -49,6 +48,9 @@ struct Annotation { /// End column within the line. end_col: usize, + /// Is this annotation derived from primary span + is_primary: bool, + /// Optional label to display adjacent to the annotation. label: Option, } @@ -65,19 +67,27 @@ pub struct StyledString { pub style: Style, } -#[derive(Copy, Clone, Debug)] +#[derive(Debug)] +pub struct StyledBuffer { + text: Vec>, + styles: Vec> +} + +#[derive(Copy, Clone, Debug, PartialEq)] pub enum Style { FileNameStyle, LineAndColumn, LineNumber, Quotation, - Underline, - Label, + UnderlinePrimary, + UnderlineSecondary, + LabelPrimary, + LabelSecondary, NoStyle, } use self::Style::*; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum RenderedLineKind { PrimaryFileName, OtherFileName, @@ -106,8 +116,7 @@ impl SnippetData { codemap: codemap.clone(), files: vec![] }; - if let Some(primary_span) = -primary_span { + if let Some(primary_span) = primary_span { let lo = codemap.lookup_char_pos(primary_span.lo); data.files.push( FileInfo { @@ -129,7 +138,7 @@ primary_span { }; self.file(&file_lines.file) - .push_lines(&file_lines.lines, label); + .push_lines(&file_lines.lines, span, label); } fn file(&mut self, file_map: &Rc) -> &mut FileInfo { @@ -242,12 +251,97 @@ impl RenderedLineKind { } } +impl StyledBuffer { + fn new() -> StyledBuffer { + StyledBuffer { text: vec![], styles: vec![] } + } + + fn render(&self, source_kind: RenderedLineKind) -> Vec { + let mut output: Vec = vec![]; + let mut styled_vec: Vec = vec![]; + + for (row, row_style) in self.text.iter().zip(&self.styles) { + let mut current_style = NoStyle; + let mut current_text = String::new(); + + for (&c, &s) in row.iter().zip(row_style) { + if s != current_style { + if !current_text.is_empty() { + styled_vec.push(StyledString { text: current_text, style: current_style }); + } + current_style = s; + current_text = String::new(); + } + current_text.push(c); + } + if !current_text.is_empty() { + styled_vec.push(StyledString { text: current_text, style: current_style }); + } + + if output.is_empty() { + //We know our first output line is source and the rest are highlights and labels + output.push(RenderedLine { text: styled_vec, kind: source_kind.clone() }); + } else { + output.push(RenderedLine { text: styled_vec, kind: Annotations }); + } + styled_vec = vec![]; + } + + output + } + + fn putc(&mut self, line: usize, col: usize, chr: char, style: Style) { + while line >= self.text.len() { + self.text.push(vec![]); + self.styles.push(vec![]); + } + + if col < self.text[line].len() { + self.text[line][col] = chr; + self.styles[line][col] = style; + } else { + while self.text[line].len() < col { + self.text[line].push(' '); + self.styles[line].push(NoStyle); + } + self.text[line].push(chr); + self.styles[line].push(style); + } + } + + fn puts(&mut self, line: usize, col: usize, string: &str, style: Style) { + let mut n = col; + for c in string.chars() { + self.putc(line, n, c, style); + n += 1; + } + } + + fn set_style(&mut self, line: usize, col: usize, style: Style) { + if self.styles.len() > line && self.styles[line].len() > col { + self.styles[line][col] = style; + } + } + + fn append(&mut self, line: usize, string: &str, style: Style) { + if line >= self.text.len() { + self.puts(line, 0, string, style); + } else { + let col = self.text[line].len(); + self.puts(line, col, string, style); + } + } +} + impl FileInfo { fn push_lines(&mut self, lines: &[LineInfo], + span: Span, label: Option) { assert!(lines.len() > 0); + let is_primary = Some(span) == self.primary_span; + // If a span covers multiple lines, just put the label on the // first one. This is a sort of arbitrary choice and not // obviously correct. @@ -255,12 +349,14 @@ impl FileInfo { let index = self.ensure_source_line(line0.line_index); self.lines[index].push_annotation(line0.start_col, line0.end_col, + is_primary, label); for line in remaining_lines { if line.end_col > line.start_col { let index = self.ensure_source_line(line.line_index); self.lines[index].push_annotation(line.start_col, line.end_col, + is_primary, None); } } @@ -373,7 +469,7 @@ impl FileInfo { text: lo.file.name.clone(), style: FileNameStyle, }, StyledString { - text: format!(" ({}:{})", lo.line, lo.col.0 + 1), + text: format!(":{}:{}", lo.line, lo.col.0 + 1), style: LineAndColumn, }], kind: PrimaryFileName, @@ -414,12 +510,10 @@ impl FileInfo { None => annotation.start_col == 0 && annotation.end_col == source_string.len() } - } - else { + } else { false } - } - else { + } else { false }; @@ -438,12 +532,10 @@ impl FileInfo { prev_ends_at_eol = annotation_ends_at_eol; } - } - else { + } else { if group.len() > 1 { output.push(RenderedLine::from((String::new(), NoStyle, Elision))); - } - else { + } else { let mut v: Vec = group.iter().flat_map(|line| self.render_line(line)).collect(); output.append(&mut v); @@ -463,14 +555,20 @@ impl FileInfo { file: self.file.clone(), line_index: line.line_index, }; + + let mut styled_buffer = StyledBuffer::new(); + + // First create the source line we will highlight. + styled_buffer.append(0, &source_string, Quotation); + if line.annotations.is_empty() { - return vec![RenderedLine::from((source_string, Quotation, source_kind))]; + return styled_buffer.render(source_kind); } // We want to display like this: // // vec.push(vec.pop().unwrap()); - // ~~~ ~~~ ~ previous borrow ends here + // --- ^^^ _ previous borrow ends here // | | // | error occurs here // previous borrow of `vec` occurs here @@ -478,7 +576,7 @@ impl FileInfo { // But there are some weird edge cases to be aware of: // // vec.push(vec.pop().unwrap()); - // ~~~~~~~~ ~ previous borrow ends here + // -------- - previous borrow ends here // || // |this makes no sense // previous borrow of `vec` occurs here @@ -486,16 +584,21 @@ impl FileInfo { // For this reason, we group the lines into "highlight lines" // and "annotations lines", where the highlight lines have the `~`. - let mut highlight_line = Self::whitespace(&source_string); + //let mut highlight_line = Self::whitespace(&source_string); // Sort the annotations by (start, end col) let mut annotations = line.annotations.clone(); annotations.sort(); - // First create the highlight line. + // Next, create the highlight line. for annotation in &annotations { for p in annotation.start_col .. annotation.end_col { - highlight_line[p] = '~'; + if annotation.is_primary { + styled_buffer.putc(1, p, '^', UnderlinePrimary); + styled_buffer.set_style(0, p, UnderlinePrimary); + } else { + styled_buffer.putc(1, p, '-', UnderlineSecondary); + } } } @@ -507,15 +610,14 @@ impl FileInfo { // If there are no annotations that need text, we're done. if labeled_annotations.is_empty() { - return vec![RenderedLine::from((source_string, Quotation, source_kind)), - RenderedLine::from((highlight_line, Underline, Annotations))]; + return styled_buffer.render(source_kind); } // Now add the text labels. We try, when possible, to stick the rightmost // annotation at the end of the highlight line: // // vec.push(vec.pop().unwrap()); - // ~~~ ~~~ ~ previous borrow ends here + // --- --- - previous borrow ends here // // But sometimes that's not possible because one of the other // annotations overlaps it. For example, from the test @@ -523,21 +625,21 @@ impl FileInfo { // (written on distinct lines for clarity): // // fn foo(x: u32) { - // ~~~~~~~~~~~~~~ - // ~ + // -------------- + // - // // In this case, we can't stick the rightmost-most label on // the highlight line, or we would get: // // fn foo(x: u32) { - // ~~~~~~~~ x_span + // -------- x_span // | // fn_span // // which is totally weird. Instead we want: // // fn foo(x: u32) { - // ~~~~~~~~~~~~~~ + // -------------- // | | // | x_span // fn_span @@ -547,102 +649,57 @@ impl FileInfo { // use the "hang below" version, so we can at least make it // clear where the span *starts*. let mut labeled_annotations = &labeled_annotations[..]; - let mut highlight_label = String::new(); match labeled_annotations.split_last().unwrap() { (last, previous) => { if previous.iter() .chain(&unlabeled_annotations) .all(|a| !overlaps(a, last)) { - // there shouldn't be any squiggles after last.end_col, - // but there might be some whitespace; cut it off - if highlight_line.len() > last.end_col { - highlight_line.truncate(last.end_col); - } - // append the label afterwards; we keep it in a separate // string - highlight_label = format!(" {}", last.label.as_ref().unwrap()); + let highlight_label: String = format!(" {}", last.label.as_ref().unwrap()); + if last.is_primary { + styled_buffer.append(1, &highlight_label, LabelPrimary); + } else { + styled_buffer.append(1, &highlight_label, LabelSecondary); + } labeled_annotations = previous; } } } - // The ~ line begins with the ~'s, which are Underline, and - // ends with a (possibly empty) label. - let highlight_render_line = - RenderedLine::from( - (highlight_line, Underline, - highlight_label, Label, - Annotations)); - // If that's the last annotation, we're done if labeled_annotations.is_empty() { - return vec![ - RenderedLine::from((source_string, Quotation, source_kind)), - highlight_render_line, - ]; + return styled_buffer.render(source_kind); } - // Otherwise, total lines will be 1 + number of remaining. - let total_lines = 1 + labeled_annotations.len(); - - // Make one string per line. - let mut extra_lines: Vec<_> = - (0..total_lines).map(|_| Self::whitespace(&source_string)).collect(); - for (index, annotation) in labeled_annotations.iter().enumerate() { // Leave: // - 1 extra line // - One line for each thing that comes after let comes_after = labeled_annotations.len() - index - 1; - let blank_lines = 1 + comes_after; + let blank_lines = 3 + comes_after; // For each blank line, draw a `|` at our column. The // text ought to be long enough for this. - for index in 0..blank_lines { - extra_lines[index][annotation.start_col] = '|'; + for index in 2..blank_lines { + if annotation.is_primary { + styled_buffer.putc(index, annotation.start_col, '|', UnderlinePrimary); + } else { + styled_buffer.putc(index, annotation.start_col, '|', UnderlineSecondary); + } } - // After we write the `|`, write the label. - Self::write_label(&mut extra_lines[blank_lines], - annotation.start_col, - annotation.label.as_ref().unwrap()); - } - - // Convert the Vec into String - let extra_strings = - extra_lines.into_iter() - .map(|v| RenderedLine::from((v, Label, Annotations))); - - iter::once(RenderedLine::from((source_string, Quotation, source_kind))) - .chain(Some(highlight_render_line)) - .chain(extra_strings) - .collect() - } - - fn write_label(text: &mut Vec, - position: usize, - label: &str) - { - if text.len() > position { - text.truncate(position); - } - - while text.len() < position { - text.push(' '); + if annotation.is_primary { + styled_buffer.puts(blank_lines, annotation.start_col, + annotation.label.as_ref().unwrap(), LabelPrimary); + } else { + styled_buffer.puts(blank_lines, annotation.start_col, + annotation.label.as_ref().unwrap(), LabelSecondary); + } } - text.extend(label.chars()); - } - - fn whitespace(str: &str) -> Vec { - str.chars() - .map(|c| match c { - '\t' => '\t', - _ => ' ', - }) - .collect() + styled_buffer.render(source_kind) } } @@ -724,10 +781,12 @@ impl Line { fn push_annotation(&mut self, start: CharPos, end: CharPos, + is_primary: bool, label: Option) { self.annotations.push(Annotation { start_col: start.0, end_col: end.0, + is_primary: is_primary, label: label, }); } diff --git a/src/libsyntax/errors/snippet/test.rs b/src/libsyntax/errors/snippet/test.rs index 8da8e2ccad690..bb048349d8f72 100644 --- a/src/libsyntax/errors/snippet/test.rs +++ b/src/libsyntax/errors/snippet/test.rs @@ -105,7 +105,7 @@ fn foo() { assert_eq!(&text[..], &r#" >>>> foo.rs 3 |> vec.push(vec.pop().unwrap()); - |> ~~~ ~~~ ~ previous borrow ends here + |> --- --- - previous borrow ends here |> | | |> | error occurs here |> previous borrow of `vec` occurs here @@ -172,20 +172,20 @@ fn bar() { // Note that the `|>` remain aligned across both files: assert_eq!(&text[..], &r#" - --> foo.rs:3:14: 3:17 + --> foo.rs:3:14 3 |> vec.push(vec.pop().unwrap()); - |> ~~~ ~~~ ~ c + |> --- ^^^ - c |> | | |> | b |> a >>>>>> bar.rs 17 |> vec.push(); - |> ~~~ ~ f + |> --- - f |> | |> d ... 21 |> vec.pop().unwrap()); - |> ~~~ e + |> --- e "#[1..]); } @@ -224,13 +224,13 @@ fn foo() { assert_eq!(&text[..], &r#" >>>>>> foo.rs 3 |> let name = find_id(&data, 22).unwrap(); - |> ~~~~ immutable borrow begins here + |> ---- immutable borrow begins here ... 6 |> data.push(Data { name: format!("Hera"), id: 66 }); - |> ~~~~ mutable borrow occurs here + |> ---- mutable borrow occurs here ... 11 |> } - |> ~ immutable borrow ends here + |> - immutable borrow ends here "#[1..]); } @@ -263,7 +263,7 @@ fn foo() { assert_eq!(&text[..], &r#" >>>> foo.rs 3 |> vec.push(vec.pop().unwrap()); - |> ~~~~~~~~ ~~~~~~ D + |> -------- ------ D |> || |> |C |> A @@ -299,7 +299,7 @@ fn foo() { assert_eq!(&text[..], &r#" >>>> foo.rs 3 |> vec.push(vec.pop().unwrap()); - |> ~~~ ~~~ ~ previous borrow ends here + |> --- --- - previous borrow ends here |> | | |> | error occurs here |> previous borrow of `vec` occurs here @@ -337,10 +337,10 @@ fn foo() { assert_eq!(&text[..], &r#" >>>>>> foo.rs 4 |> let mut vec2 = vec; - |> ~~~ `vec` moved here because it has type `collections::vec::Vec`, which is moved by default + |> --- `vec` moved here because it has type `collections::vec::Vec`, which is moved by default ... 9 |> vec.push(7); - |> ~~~ use of moved value: `vec` + |> --- use of moved value: `vec` "#[1..]); } @@ -373,9 +373,9 @@ fn foo() { assert_eq!(text, &r#" >>>> foo.rs 3 |> let mut vec = vec![0, 1, 2]; - |> ~~~ ~~~ + |> --- --- 4 |> let mut vec2 = vec; - |> ~~~ ~~~ + |> --- --- "#[1..]); } @@ -404,7 +404,7 @@ impl SomeTrait for () { assert_eq!(text, &r#" >>>>>> foo.rs 3 |> fn foo(x: u32) { - |> ~~~~~~~~~~~~~~~~ + |> ---------------- ... "#[1..]); } @@ -434,7 +434,7 @@ fn span_overlap_label() { assert_eq!(text, &r#" >>>> foo.rs 2 |> fn foo(x: u32) { - |> ~~~~~~~~~~~~~~ + |> -------------- |> | | |> | x_span |> fn_span @@ -468,7 +468,7 @@ fn span_overlap_label2() { assert_eq!(text, &r#" >>>> foo.rs 2 |> fn foo(x: u32) { - |> ~~~~~~~~~~~~~~ + |> -------------- |> | | |> | x_span |> fn_span @@ -513,12 +513,12 @@ fn span_overlap_label3() { assert_eq!(text, &r#" >>>> foo.rs 3 |> let closure = || { - |> ~~~~ foo + |> ---- foo 4 |> inner - |> ~~~~~~~~~~~~~~~~ + |> ---------------- |> | |> bar 5 |> }; - |> ~~~~~~~~ + |> -------- "#[1..]); }