1 // Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
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.
11 // Code for annotating snippets.
13 use codemap::{CharPos, CodeMap, FileMap, LineInfo, Span};
14 use errors::check_old_skool;
22 pub struct SnippetData {
31 /// The "primary file", if any, gets a `-->` marker instead of
32 /// `>>>`, and has a line-number/column printed and not just a
33 /// filename. It appears first in the listing. It is known to
34 /// contain at least one primary span, though primary spans (which
35 /// are designated with `^^^`) may also occur in other files.
36 primary_span: Option<Span>,
41 #[derive(Clone, Debug)]
44 annotations: Vec<Annotation>,
47 #[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
49 /// Start column, 0-based indexing -- counting *characters*, not
50 /// utf-8 bytes. Note that it is important that this field goes
51 /// first, so that when we sort, we sort orderings by start
55 /// End column within the line (exclusive)
58 /// Is this annotation derived from primary span
61 /// Optional label to display adjacent to the annotation.
62 label: Option<String>,
66 pub struct RenderedLine {
67 pub text: Vec<StyledString>,
68 pub kind: RenderedLineKind,
72 pub struct StyledString {
78 pub struct StyledBuffer {
80 styles: Vec<Vec<Style>>
83 #[derive(Copy, Clone, Debug, PartialEq)]
96 #[derive(Debug, Clone)]
97 pub enum RenderedLineKind {
109 pub fn new(codemap: Rc<CodeMap>,
110 primary_span: Option<Span>) // (*)
112 // (*) The primary span indicates the file that must appear
113 // first, and which will have a line number etc in its
114 // name. Outside of tests, this is always `Some`, but for many
115 // tests it's not relevant to test this portion of the logic,
116 // and it's tedious to pick a primary span (read: tedious to
117 // port older tests that predate the existence of a primary
120 debug!("SnippetData::new(primary_span={:?})", primary_span);
122 let mut data = SnippetData {
123 codemap: codemap.clone(),
126 if let Some(primary_span) = primary_span {
127 let lo = codemap.lookup_char_pos(primary_span.lo);
131 primary_span: Some(primary_span),
138 pub fn push(&mut self, span: Span, is_primary: bool, label: Option<String>) {
139 debug!("SnippetData::push(span={:?}, is_primary={}, label={:?})",
140 span, is_primary, label);
142 let file_lines = match self.codemap.span_to_lines(span) {
143 Ok(file_lines) => file_lines,
145 // ignore unprintable spans completely.
150 self.file(&file_lines.file)
151 .push_lines(&file_lines.lines, is_primary, label);
154 fn file(&mut self, file_map: &Rc<FileMap>) -> &mut FileInfo {
155 let index = self.files.iter().position(|f| f.file.name == file_map.name);
156 if let Some(index) = index {
157 return &mut self.files[index];
162 file: file_map.clone(),
166 self.files.last_mut().unwrap()
169 pub fn render_lines(&self) -> Vec<RenderedLine> {
170 debug!("SnippetData::render_lines()");
172 let mut rendered_lines: Vec<_> =
174 .flat_map(|f| f.render_file_lines(&self.codemap))
176 prepend_prefixes(&mut rendered_lines);
177 trim_lines(&mut rendered_lines);
182 pub trait StringSource {
183 fn make_string(self) -> String;
186 impl StringSource for String {
187 fn make_string(self) -> String {
192 impl StringSource for Vec<char> {
193 fn make_string(self) -> String {
194 self.into_iter().collect()
198 impl<S> From<(S, Style, RenderedLineKind)> for RenderedLine
199 where S: StringSource
201 fn from((text, style, kind): (S, Style, RenderedLineKind)) -> Self {
203 text: vec![StyledString {
204 text: text.make_string(),
212 impl<S1,S2> From<(S1, Style, S2, Style, RenderedLineKind)> for RenderedLine
213 where S1: StringSource, S2: StringSource
215 fn from(tuple: (S1, Style, S2, Style, RenderedLineKind)) -> Self {
216 let (text1, style1, text2, style2, kind) = tuple;
220 text: text1.make_string(),
224 text: text2.make_string(),
234 fn trim_last(&mut self) {
235 if let Some(last_text) = self.text.last_mut() {
236 let len = last_text.text.trim_right().len();
237 last_text.text.truncate(len);
242 impl RenderedLineKind {
243 fn prefix(&self) -> StyledString {
245 RenderedLineKind::SourceText { file: _, line_index } =>
247 text: format!("{}", line_index + 1),
248 style: Style::LineNumber,
250 RenderedLineKind::Elision =>
252 text: String::from("..."),
253 style: Style::LineNumber,
255 RenderedLineKind::PrimaryFileName |
256 RenderedLineKind::OtherFileName |
257 RenderedLineKind::Annotations =>
259 text: String::from(""),
260 style: Style::LineNumber,
267 fn new() -> StyledBuffer {
268 StyledBuffer { text: vec![], styles: vec![] }
271 fn render(&self, source_kind: RenderedLineKind) -> Vec<RenderedLine> {
272 let mut output: Vec<RenderedLine> = vec![];
273 let mut styled_vec: Vec<StyledString> = vec![];
275 for (row, row_style) in self.text.iter().zip(&self.styles) {
276 let mut current_style = Style::NoStyle;
277 let mut current_text = String::new();
279 for (&c, &s) in row.iter().zip(row_style) {
280 if s != current_style {
281 if !current_text.is_empty() {
282 styled_vec.push(StyledString { text: current_text, style: current_style });
285 current_text = String::new();
287 current_text.push(c);
289 if !current_text.is_empty() {
290 styled_vec.push(StyledString { text: current_text, style: current_style });
293 if output.is_empty() {
294 //We know our first output line is source and the rest are highlights and labels
295 output.push(RenderedLine { text: styled_vec, kind: source_kind.clone() });
297 output.push(RenderedLine { text: styled_vec, kind: RenderedLineKind::Annotations });
305 fn putc(&mut self, line: usize, col: usize, chr: char, style: Style) {
306 while line >= self.text.len() {
307 self.text.push(vec![]);
308 self.styles.push(vec![]);
311 if col < self.text[line].len() {
312 self.text[line][col] = chr;
313 self.styles[line][col] = style;
315 let mut i = self.text[line].len();
317 let s = match self.text[0].get(i) {
321 self.text[line].push(s);
322 self.styles[line].push(Style::NoStyle);
325 self.text[line].push(chr);
326 self.styles[line].push(style);
330 fn puts(&mut self, line: usize, col: usize, string: &str, style: Style) {
332 for c in string.chars() {
333 self.putc(line, n, c, style);
338 fn set_style(&mut self, line: usize, col: usize, style: Style) {
339 if self.styles.len() > line && self.styles[line].len() > col {
340 self.styles[line][col] = style;
344 fn append(&mut self, line: usize, string: &str, style: Style) {
345 if line >= self.text.len() {
346 self.puts(line, 0, string, style);
348 let col = self.text[line].len();
349 self.puts(line, col, string, style);
355 fn push_lines(&mut self,
358 label: Option<String>) {
359 assert!(lines.len() > 0);
361 // If a span covers multiple lines, we reduce it to a single
362 // point at the start of the span. This means that instead
363 // of producing output like this:
367 // 2 |> fn conflicting_items<'grammar>(state: &LR0State<'grammar>)
368 // |> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
369 // 3 |> -> Set<LR0Item<'grammar>>
370 // |> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
378 // 2 |> fn conflicting_items<'grammar>(state: &LR0State<'grammar>)
382 // Basically, although this loses information, multi-line spans just
385 let (line, start_col, mut end_col) = if lines.len() == 1 {
386 (lines[0].line_index, lines[0].start_col, lines[0].end_col)
388 (lines[0].line_index, lines[0].start_col, CharPos(lines[0].start_col.0 + 1))
391 // Watch out for "empty spans". If we get a span like 6..6, we
392 // want to just display a `^` at 6, so convert that to
393 // 6..7. This is degenerate input, but it's best to degrade
394 // gracefully -- and the parser likes to suply a span like
395 // that for EOF, in particular.
396 if start_col == end_col {
400 let index = self.ensure_source_line(line);
401 self.lines[index].push_annotation(start_col,
407 /// Ensure that we have a `Line` struct corresponding to
408 /// `line_index` in the file. If we already have some other lines,
409 /// then this will add the intervening lines to ensure that we
410 /// have a complete snippet. (Note that when we finally display,
411 /// some of those lines may be elided.)
412 fn ensure_source_line(&mut self, line_index: usize) -> usize {
413 if self.lines.is_empty() {
414 self.lines.push(Line::new(line_index));
418 // Find the range of lines we have thus far.
419 let first_line_index = self.lines.first().unwrap().line_index;
420 let last_line_index = self.lines.last().unwrap().line_index;
421 assert!(first_line_index <= last_line_index);
423 // If the new line is lower than all the lines we have thus
424 // far, then insert the new line and any intervening lines at
425 // the front. In a silly attempt at micro-optimization, we
426 // don't just call `insert` repeatedly, but instead make a new
427 // (empty) vector, pushing the new lines onto it, and then
428 // appending the old vector.
429 if line_index < first_line_index {
430 let lines = mem::replace(&mut self.lines, vec![]);
432 (line_index .. first_line_index)
433 .map(|line| Line::new(line))
438 // If the new line comes after the ones we have so far, insert
440 if line_index > last_line_index {
442 (last_line_index+1 .. line_index+1)
443 .map(|line| Line::new(line)));
444 return self.lines.len() - 1;
447 // Otherwise it should already exist.
448 return line_index - first_line_index;
451 fn render_file_lines(&self, codemap: &Rc<CodeMap>) -> Vec<RenderedLine> {
452 let old_school = check_old_skool();
454 // As a first step, we elide any instance of more than one
455 // continuous unannotated line.
457 let mut lines_iter = self.lines.iter();
458 let mut output = vec![];
460 // First insert the name of the file.
462 match self.primary_span {
464 let lo = codemap.lookup_char_pos(span.lo);
465 output.push(RenderedLine {
466 text: vec![StyledString {
467 text: lo.file.name.clone(),
468 style: Style::FileNameStyle,
470 text: format!(":{}:{}", lo.line, lo.col.0 + 1),
471 style: Style::LineAndColumn,
473 kind: RenderedLineKind::PrimaryFileName,
477 output.push(RenderedLine {
478 text: vec![StyledString {
479 text: self.file.name.clone(),
480 style: Style::FileNameStyle,
482 kind: RenderedLineKind::OtherFileName,
488 let mut next_line = lines_iter.next();
489 while next_line.is_some() {
490 // Consume lines with annotations.
491 while let Some(line) = next_line {
492 if line.annotations.is_empty() { break; }
494 let mut rendered_lines = self.render_line(line);
495 assert!(!rendered_lines.is_empty());
497 match self.primary_span {
499 let lo = codemap.lookup_char_pos(span.lo);
500 rendered_lines[0].text.insert(0, StyledString {
501 text: format!(":{} ", lo.line),
502 style: Style::LineAndColumn,
504 rendered_lines[0].text.insert(0, StyledString {
505 text: lo.file.name.clone(),
506 style: Style::FileNameStyle,
509 rendered_lines[0].text[0].text.len() +
510 rendered_lines[0].text[1].text.len();
511 assert!(rendered_lines.len() >= 2,
512 "no annotations resulted from: {:?}",
514 for i in 1..rendered_lines.len() {
515 rendered_lines[i].text.insert(0, StyledString {
516 text: vec![" "; gap_amount].join(""),
517 style: Style::NoStyle
524 output.append(&mut rendered_lines);
525 next_line = lines_iter.next();
528 // Emit lines without annotations, but only if they are
529 // followed by a line with an annotation.
530 let unannotated_line = next_line;
531 let mut unannotated_lines = 0;
532 while let Some(line) = next_line {
533 if !line.annotations.is_empty() { break; }
534 unannotated_lines += 1;
535 next_line = lines_iter.next();
537 if unannotated_lines > 1 {
538 output.push(RenderedLine::from((String::new(),
540 RenderedLineKind::Elision)));
541 } else if let Some(line) = unannotated_line {
542 output.append(&mut self.render_line(line));
549 fn render_line(&self, line: &Line) -> Vec<RenderedLine> {
550 let old_school = check_old_skool();
551 let source_string = self.file.get_line(line.line_index)
553 let source_kind = RenderedLineKind::SourceText {
554 file: self.file.clone(),
555 line_index: line.line_index,
558 let mut styled_buffer = StyledBuffer::new();
560 // First create the source line we will highlight.
561 styled_buffer.append(0, &source_string, Style::Quotation);
563 if line.annotations.is_empty() {
564 return styled_buffer.render(source_kind);
567 // We want to display like this:
569 // vec.push(vec.pop().unwrap());
570 // --- ^^^ _ previous borrow ends here
572 // | error occurs here
573 // previous borrow of `vec` occurs here
575 // But there are some weird edge cases to be aware of:
577 // vec.push(vec.pop().unwrap());
578 // -------- - previous borrow ends here
580 // |this makes no sense
581 // previous borrow of `vec` occurs here
583 // For this reason, we group the lines into "highlight lines"
584 // and "annotations lines", where the highlight lines have the `~`.
586 //let mut highlight_line = Self::whitespace(&source_string);
588 // Sort the annotations by (start, end col)
589 let mut annotations = line.annotations.clone();
592 // Next, create the highlight line.
593 for annotation in &annotations {
595 for p in annotation.start_col .. annotation.end_col {
596 if p == annotation.start_col {
597 styled_buffer.putc(1, p, '^',
598 if annotation.is_primary {
599 Style::UnderlinePrimary
601 Style::UnderlineSecondary
605 styled_buffer.putc(1, p, '~',
606 if annotation.is_primary {
607 Style::UnderlinePrimary
609 Style::UnderlineSecondary
615 for p in annotation.start_col .. annotation.end_col {
616 if annotation.is_primary {
617 styled_buffer.putc(1, p, '^', Style::UnderlinePrimary);
618 styled_buffer.set_style(0, p, Style::UnderlinePrimary);
620 styled_buffer.putc(1, p, '-', Style::UnderlineSecondary);
626 // Now we are going to write labels in. To start, we'll exclude
627 // the annotations with no labels.
628 let (labeled_annotations, unlabeled_annotations): (Vec<_>, _) =
629 annotations.into_iter()
630 .partition(|a| a.label.is_some());
632 // If there are no annotations that need text, we're done.
633 if labeled_annotations.is_empty() {
634 return styled_buffer.render(source_kind);
637 return styled_buffer.render(source_kind);
640 // Now add the text labels. We try, when possible, to stick the rightmost
641 // annotation at the end of the highlight line:
643 // vec.push(vec.pop().unwrap());
644 // --- --- - previous borrow ends here
646 // But sometimes that's not possible because one of the other
647 // annotations overlaps it. For example, from the test
648 // `span_overlap_label`, we have the following annotations
649 // (written on distinct lines for clarity):
655 // In this case, we can't stick the rightmost-most label on
656 // the highlight line, or we would get:
663 // which is totally weird. Instead we want:
671 // which is...less weird, at least. In fact, in general, if
672 // the rightmost span overlaps with any other span, we should
673 // use the "hang below" version, so we can at least make it
674 // clear where the span *starts*.
675 let mut labeled_annotations = &labeled_annotations[..];
676 match labeled_annotations.split_last().unwrap() {
677 (last, previous) => {
679 .chain(&unlabeled_annotations)
680 .all(|a| !overlaps(a, last))
682 // append the label afterwards; we keep it in a separate
684 let highlight_label: String = format!(" {}", last.label.as_ref().unwrap());
686 styled_buffer.append(1, &highlight_label, Style::LabelPrimary);
688 styled_buffer.append(1, &highlight_label, Style::LabelSecondary);
690 labeled_annotations = previous;
695 // If that's the last annotation, we're done
696 if labeled_annotations.is_empty() {
697 return styled_buffer.render(source_kind);
700 for (index, annotation) in labeled_annotations.iter().enumerate() {
703 // - One line for each thing that comes after
704 let comes_after = labeled_annotations.len() - index - 1;
705 let blank_lines = 3 + comes_after;
707 // For each blank line, draw a `|` at our column. The
708 // text ought to be long enough for this.
709 for index in 2..blank_lines {
710 if annotation.is_primary {
711 styled_buffer.putc(index, annotation.start_col, '|', Style::UnderlinePrimary);
713 styled_buffer.putc(index, annotation.start_col, '|', Style::UnderlineSecondary);
717 if annotation.is_primary {
718 styled_buffer.puts(blank_lines, annotation.start_col,
719 annotation.label.as_ref().unwrap(), Style::LabelPrimary);
721 styled_buffer.puts(blank_lines, annotation.start_col,
722 annotation.label.as_ref().unwrap(), Style::LabelSecondary);
726 styled_buffer.render(source_kind)
730 fn prepend_prefixes(rendered_lines: &mut [RenderedLine]) {
731 let old_school = check_old_skool();
736 let prefixes: Vec<_> =
737 rendered_lines.iter()
738 .map(|rl| rl.kind.prefix())
741 // find the max amount of spacing we need; add 1 to
742 // p.text.len() to leave space between the prefix and the
746 .map(|p| if p.text.len() == 0 { 0 } else { p.text.len() + 1 })
750 // Ensure we insert at least one character of padding, so that the
751 // `-->` arrows can fit etc.
752 let padding_len = cmp::max(padding_len, 1);
754 for (mut prefix, line) in prefixes.into_iter().zip(rendered_lines) {
755 let extra_spaces = (prefix.text.len() .. padding_len).map(|_| ' ');
756 prefix.text.extend(extra_spaces);
758 RenderedLineKind::Elision => {
759 line.text.insert(0, prefix);
761 RenderedLineKind::PrimaryFileName => {
766 let dashes = (0..padding_len - 1).map(|_| ' ')
771 line.text.insert(0, StyledString {text: dashes.collect(),
772 style: Style::LineNumber})
774 RenderedLineKind::OtherFileName => {
779 let dashes = (0..padding_len - 1).map(|_| ' ')
784 line.text.insert(0, StyledString {text: dashes.collect(),
785 style: Style::LineNumber})
788 line.text.insert(0, prefix);
789 line.text.insert(1, StyledString {text: String::from("|> "),
790 style: Style::LineNumber})
796 fn trim_lines(rendered_lines: &mut [RenderedLine]) {
797 for line in rendered_lines {
798 while !line.text.is_empty() {
800 if line.text.last().unwrap().text.is_empty() {
810 fn new(line_index: usize) -> Line {
812 line_index: line_index,
817 fn push_annotation(&mut self,
821 label: Option<String>) {
822 self.annotations.push(Annotation {
825 is_primary: is_primary,
831 fn overlaps(a1: &Annotation,
835 (a2.start_col .. a2.end_col).contains(a1.start_col) ||
836 (a1.start_col .. a1.end_col).contains(a2.start_col)