about summary refs log tree commit diff
path: root/src/librustc_errors
diff options
context:
space:
mode:
authorEsteban Küber <esteban@kuber.com.ar>2016-10-23 17:22:06 -0700
committerEsteban Küber <esteban@kuber.com.ar>2016-11-22 13:42:36 -0800
commiteb53ca3aad616069cb8f6f8fff71c27e9ba9640c (patch)
tree8b305f44c0527577d57be9cc693465dd3b74f7e5 /src/librustc_errors
parentfb122199aac1fd4f9a3c133e25791a9fcb2a6b83 (diff)
downloadrust-eb53ca3aad616069cb8f6f8fff71c27e9ba9640c.tar.gz
rust-eb53ca3aad616069cb8f6f8fff71c27e9ba9640c.zip
Show multiline spans in full if short enough
When dealing with multiline spans that span few lines, show the complete
span instead of restricting to the first character of the first line.

For example, instead of:

```
% ./rustc foo.rs
error[E0277]: the trait bound `{integer}: std::ops::Add<()>` is not satisfied
  --> foo.rs:13:9
   |
13 |    foo(1 + bar(x,
   |        ^ trait `{integer}: std::ops::Add<()>` not satisfied
   |
```

show

```
% ./rustc foo.rs
error[E0277]: the trait bound `{integer}: std::ops::Add<()>` is not satisfied
  --> foo.rs:13:9
   |
13 |      foo(1 + bar(x,
   |  ________^ starting here...
14 | |            y),
   | |_____________^ ...ending here: trait `{integer}: std::ops::Add<()>` not satisfied
   |
```
Diffstat (limited to 'src/librustc_errors')
-rw-r--r--src/librustc_errors/emitter.rs484
-rw-r--r--src/librustc_errors/snippet.rs103
2 files changed, 479 insertions, 108 deletions
diff --git a/src/librustc_errors/emitter.rs b/src/librustc_errors/emitter.rs
index a307e9b696d..808a1683b84 100644
--- a/src/librustc_errors/emitter.rs
+++ b/src/librustc_errors/emitter.rs
@@ -14,7 +14,7 @@ use syntax_pos::{COMMAND_LINE_SP, DUMMY_SP, FileMap, Span, MultiSpan, CharPos};
 
 use {Level, CodeSuggestion, DiagnosticBuilder, SubDiagnostic, CodeMapper};
 use RenderSpan::*;
-use snippet::{StyledString, Style, Annotation, Line};
+use snippet::{Annotation, AnnotationType, Line, StyledString, Style};
 use styled_buffer::StyledBuffer;
 
 use std::io::prelude::*;
@@ -65,6 +65,7 @@ pub struct EmitterWriter {
 struct FileWithAnnotatedLines {
     file: Rc<FileMap>,
     lines: Vec<Line>,
+    multiline_depth: usize,
 }
 
 
@@ -137,10 +138,12 @@ impl EmitterWriter {
                                 line_index: line_index,
                                 annotations: vec![ann],
                             }],
+                multiline_depth: 0,
             });
         }
 
         let mut output = vec![];
+        let mut multiline_annotations = vec![];
 
         if let Some(ref cm) = self.cm {
             for span_label in msp.span_labels() {
@@ -151,8 +154,9 @@ impl EmitterWriter {
                 let mut hi = cm.lookup_char_pos(span_label.span.hi);
                 let mut is_minimized = false;
 
-                // If the span is multi-line, simplify down to the span of one character
-                if lo.line != hi.line {
+                // If the span is long multi-line, simplify down to the span of one character
+                let max_multiline_span_length = 8;
+                if lo.line != hi.line && (hi.line - lo.line) > max_multiline_span_length {
                     hi.line = lo.line;
                     hi.col = CharPos(lo.col.0 + 1);
                     is_minimized = true;
@@ -163,22 +167,102 @@ impl EmitterWriter {
                 // 6..7. This is degenerate input, but it's best to degrade
                 // gracefully -- and the parser likes to supply a span like
                 // that for EOF, in particular.
-                if lo.col == hi.col {
+                if lo.col == hi.col && lo.line == hi.line {
                     hi.col = CharPos(lo.col.0 + 1);
                 }
 
-                add_annotation_to_file(&mut output,
-                                       lo.file,
-                                       lo.line,
-                                       Annotation {
-                                           start_col: lo.col.0,
-                                           end_col: hi.col.0,
-                                           is_primary: span_label.is_primary,
-                                           is_minimized: is_minimized,
-                                           label: span_label.label.clone(),
-                                       });
+                let mut ann = Annotation {
+                    start_col: lo.col.0,
+                    end_col: hi.col.0,
+                    is_primary: span_label.is_primary,
+                    label: span_label.label.clone(),
+                    annotation_type: AnnotationType::Singleline,
+                };
+                if is_minimized {
+                    ann.annotation_type = AnnotationType::Minimized;
+                } else if lo.line != hi.line {
+                    ann.annotation_type = AnnotationType::Multiline {
+                        depth: 1,
+                        line_start: lo.line,
+                        line_end: hi.line,
+                    };
+                    multiline_annotations.push((lo.file.clone(), ann.clone()));
+                };
+
+                if !ann.is_multiline() {
+                    add_annotation_to_file(&mut output,
+                                           lo.file,
+                                           lo.line,
+                                           ann);
+                }
+            }
+        }
+
+        // Find overlapping multiline annotations, put them at different depths
+        multiline_annotations.sort_by(|a, b| {
+            if let AnnotationType::Multiline {
+                line_start: a_start,
+                line_end: a_end,
+                ..
+            } = a.1.annotation_type {
+                if let AnnotationType::Multiline {
+                    line_start: b_start,
+                    line_end: b_end,
+                    ..
+                } = b.1.annotation_type {
+                    (a_start, a_end).cmp(&(b_start, b_end))
+                } else {
+                    panic!("tried to sort multiline annotations, but found `{:?}`", b)
+                }
+            } else {
+                panic!("tried to sort multiline annotations, but found `{:?}`", a)
+            }
+        });
+        for item in multiline_annotations.clone() {
+            let ann = item.1;
+            if let AnnotationType::Multiline {line_start, line_end, ..} = ann.annotation_type {
+                for item in multiline_annotations.iter_mut() {
+                    let ref mut a = item.1;
+                    if let AnnotationType::Multiline {
+                        line_start: start,
+                        line_end: end,
+                        ..
+                    } = a.annotation_type {
+                        // Move all other multiline annotations overlapping with this one
+                        // one level to the right.
+                        if &ann != a && num_overlap(line_start, line_end, start, end, true) {
+                            a.annotation_type.increase_depth();
+                        } else {
+                            break;
+                        }
+                    } else {
+                        panic!("tried to find depth for multiline annotation, but found `{:?}`",
+                               ann)
+                    };
+                }
+            } else {
+                panic!("tried to find depth for multiline annotation, but found `{:?}`", ann)
+            };
+        }
+
+        let mut max_depth = 0;  // max overlapping multiline spans
+        for (file, ann) in multiline_annotations {
+            if let AnnotationType::Multiline {line_start, line_end, depth} = ann.annotation_type {
+                if depth > max_depth {
+                    max_depth = depth;
+                }
+                add_annotation_to_file(&mut output, file.clone(), line_start, ann.as_start());
+                for line in line_start + 1..line_end {
+                    add_annotation_to_file(&mut output, file.clone(), line, ann.as_line());
+                }
+                add_annotation_to_file(&mut output, file, line_end, ann.as_end());
+            } else {
+                panic!("non-multiline annotation `{:?}` in `multiline_annotations`!", ann);
             }
         }
+        for file_vec in output.iter_mut() {
+            file_vec.multiline_depth = max_depth;
+        }
         output
     }
 
@@ -186,14 +270,20 @@ impl EmitterWriter {
                           buffer: &mut StyledBuffer,
                           file: Rc<FileMap>,
                           line: &Line,
-                          width_offset: usize) {
+                          width_offset: usize,
+                          multiline_depth: usize) {
         let source_string = file.get_line(line.line_index - 1)
             .unwrap_or("");
 
         let line_offset = buffer.num_lines();
+        let code_offset = if multiline_depth == 0 {
+            width_offset
+        } else {
+            width_offset + multiline_depth + 1
+        };
 
         // First create the source line we will highlight.
-        buffer.puts(line_offset, width_offset, &source_string, Style::Quotation);
+        buffer.puts(line_offset, code_offset, &source_string, Style::Quotation);
         buffer.puts(line_offset,
                     0,
                     &(line.line_index.to_string()),
@@ -201,14 +291,10 @@ impl EmitterWriter {
 
         draw_col_separator(buffer, line_offset, width_offset - 2);
 
-        if line.annotations.is_empty() {
-            return;
-        }
-
         // 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
@@ -227,42 +313,22 @@ impl EmitterWriter {
         // Sort the annotations by (start, end col)
         let mut annotations = line.annotations.clone();
         annotations.sort();
+        annotations.reverse();
 
-        // Next, create the highlight line.
-        for annotation in &annotations {
-            for p in annotation.start_col..annotation.end_col {
-                if annotation.is_primary {
-                    buffer.putc(line_offset + 1,
-                                width_offset + p,
-                                '^',
-                                Style::UnderlinePrimary);
-                    if !annotation.is_minimized {
-                        buffer.set_style(line_offset, width_offset + p, Style::UnderlinePrimary);
-                    }
-                } else {
-                    buffer.putc(line_offset + 1,
-                                width_offset + p,
-                                '-',
-                                Style::UnderlineSecondary);
-                    if !annotation.is_minimized {
-                        buffer.set_style(line_offset, width_offset + p, Style::UnderlineSecondary);
-                    }
-                }
-            }
-        }
-        draw_col_separator(buffer, line_offset + 1, width_offset - 2);
-
-        // Now we are going to write labels in. To start, we'll exclude
-        // the annotations with no labels.
-        let (labeled_annotations, unlabeled_annotations): (Vec<_>, _) = annotations.into_iter()
-            .partition(|a| a.label.is_some());
-
-        // If there are no annotations that need text, we're done.
-        if labeled_annotations.is_empty() {
-            return;
-        }
-        // Now add the text labels. We try, when possible, to stick the rightmost
-        // annotation at the end of the highlight line:
+        // First, figure out where each label will be positioned.
+        //
+        // In the case where you have the following annotations:
+        //
+        //      vec.push(vec.pop().unwrap());
+        //      --------                    - previous borrow ends here [C]
+        //      ||
+        //      |this makes no sense [B]
+        //      previous borrow of `vec` occurs here [A]
+        //
+        // `annotations_position` will hold [(2, A), (1, B), (0, C)].
+        //
+        // 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
@@ -296,66 +362,251 @@ impl EmitterWriter {
         // the rightmost span overlaps with any other span, we should
         // use the "hang below" version, so we can at least make it
         // clear where the span *starts*.
-        let mut labeled_annotations = &labeled_annotations[..];
-        match labeled_annotations.split_last().unwrap() {
-            (last, previous) => {
-                if previous.iter()
-                    .chain(&unlabeled_annotations)
-                    .all(|a| !overlaps(a, last)) {
-                    // append the label afterwards; we keep it in a separate
-                    // string
-                    let highlight_label: String = format!(" {}", last.label.as_ref().unwrap());
-                    if last.is_primary {
-                        buffer.append(line_offset + 1, &highlight_label, Style::LabelPrimary);
-                    } else {
-                        buffer.append(line_offset + 1, &highlight_label, Style::LabelSecondary);
-                    }
-                    labeled_annotations = previous;
+        let mut annotations_position = vec![];
+        let mut line_len = 0;
+        let mut p = 0;
+        let mut ann_iter = annotations.iter().peekable();
+        while let Some(annotation) = ann_iter.next() {
+            let is_line = if let AnnotationType::MultilineLine(_) = annotation.annotation_type {
+                true
+            } else {
+                false
+            };
+            let peek = ann_iter.peek();
+            if let Some(next) = peek {
+                let next_is_line = if let AnnotationType::MultilineLine(_) = next.annotation_type {
+                    true
+                } else {
+                    false
+                };
+
+                if overlaps(next, annotation) && !is_line && !next_is_line {
+                    p += 1;
                 }
             }
+            annotations_position.push((p, annotation));
+            if let Some(next) = peek {
+                let next_is_line = if let AnnotationType::MultilineLine(_) = next.annotation_type {
+                    true
+                } else {
+                    false
+                };
+                let l = if let Some(ref label) = next.label {
+                    label.len() + 2
+                } else {
+                    0
+                };
+                if (overlaps(next, annotation) || next.end_col + l > annotation.start_col)
+                    && !is_line && !next_is_line
+                {
+                    p += 1;
+                }
+            }
+            if line_len < p {
+                line_len = p;
+            }
+        }
+        if line_len != 0 {
+            line_len += 1;
         }
 
-        // If that's the last annotation, we're done
-        if labeled_annotations.is_empty() {
+        // If there are no annotations or the only annotations on this line are
+        // MultilineLine, then there's only code being shown, stop processing.
+        if line.annotations.is_empty() || line.annotations.iter()
+            .filter(|a| {
+                // Set the multiline annotation vertical lines to the left of
+                // the code in this line.
+                if let AnnotationType::MultilineLine(depth) = a.annotation_type {
+                    buffer.putc(line_offset,
+                                width_offset + depth - 1,
+                                '|',
+                                if a.is_primary {
+                                    Style::UnderlinePrimary
+                                } else {
+                                    Style::UnderlineSecondary
+                                });
+                    false
+                } else {
+                    true
+                }
+            }).collect::<Vec<_>>().len() == 0
+        {
             return;
         }
 
-        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 = 3 + comes_after;
+        for pos in 0..line_len + 1 {
+            draw_col_separator(buffer, line_offset + pos + 1, width_offset - 2);
+            buffer.putc(line_offset + pos + 1,
+                        width_offset - 2,
+                        '|',
+                        Style::LineNumber);
+        }
+
+        // Write the horizontal lines for multiline annotations
+        // (only the first and last lines need this).
+        //
+        // After this we will have:
+        //
+        // 2 |   fn foo() {
+        //   |  __________
+        //   |
+        //   |
+        // 3 |
+        // 4 |   }
+        //   |  _
+        for &(pos, annotation) in &annotations_position {
+            let style = if annotation.is_primary {
+                Style::UnderlinePrimary
+            } else {
+                Style::UnderlineSecondary
+            };
+            let pos = pos + 1;
+            match annotation.annotation_type {
+                AnnotationType::MultilineStart(depth) |
+                AnnotationType::MultilineEnd(depth) => {
+                    draw_range(buffer,
+                               '_',
+                               line_offset + pos,
+                               width_offset + depth,
+                               code_offset + annotation.start_col,
+                               style);
+                }
+                _ => (),
+            }
+        }
 
-            // For each blank line, draw a `|` at our column. The
-            // text ought to be long enough for this.
-            for index in 2..blank_lines {
-                if annotation.is_primary {
-                    buffer.putc(line_offset + index,
-                                width_offset + annotation.start_col,
+        // Write the vertical lines for multiline spans and for labels that are
+        // on a different line as the underline.
+        //
+        // After this we will have:
+        //
+        // 2 |   fn foo() {
+        //   |  __________
+        //   | |    |
+        //   | |
+        // 3 | |
+        // 4 | | }
+        //   | |_
+        for &(pos, annotation) in &annotations_position {
+            let style = if annotation.is_primary {
+                Style::UnderlinePrimary
+            } else {
+                Style::UnderlineSecondary
+            };
+            let pos = pos + 1;
+            if pos > 1 {
+                for p in line_offset + 1..line_offset + pos + 1 {
+                    buffer.putc(p,
+                                code_offset + annotation.start_col,
                                 '|',
-                                Style::UnderlinePrimary);
+                                style);
+                }
+            }
+            match annotation.annotation_type {
+                AnnotationType::MultilineStart(depth) => {
+                    for p in line_offset + pos + 1..line_offset + line_len + 2 {
+                        buffer.putc(p,
+                                    width_offset + depth - 1,
+                                    '|',
+                                    style);
+                    }
+                }
+                AnnotationType::MultilineEnd(depth) => {
+                    for p in line_offset..line_offset + pos + 1 {
+                        buffer.putc(p,
+                                    width_offset + depth - 1,
+                                    '|',
+                                    style);
+                    }
+                }
+                AnnotationType::MultilineLine(depth) => {
+                    // the first line will have already be filled when we checked
+                    // wether there were any annotations for this line.
+                    for p in line_offset + 1..line_offset + line_len + 2 {
+                        buffer.putc(p,
+                                    width_offset + depth - 1,
+                                    '|',
+                                    style);
+                    }
+                }
+                _ => (),
+            }
+        }
+
+        // Write the labels on the annotations that actually have a label.
+        //
+        // After this we will have:
+        //
+        // 2 |   fn foo() {
+        //   |  __________ starting here...
+        //   | |    |
+        //   | |    something about `foo`
+        // 3 | |
+        // 4 | | }
+        //   | |_  ...ending here: test
+        for &(pos, annotation) in &annotations_position {
+            let style = if annotation.is_primary {
+                Style::LabelPrimary
+            } else {
+                Style::LabelSecondary
+            };
+            let (pos, col) = if pos == 0 {
+                (pos + 1, annotation.end_col + 1)
+            } else {
+                (pos + 2, annotation.start_col)
+            };
+            if let Some(ref label) = annotation.label {
+                buffer.puts(line_offset + pos,
+                            code_offset + col,
+                            &label,
+                            style);
+            }
+        }
+
+        // Sort from biggest span to smallest span so that smaller spans are
+        // represented in the output:
+        //
+        // x | fn foo()
+        //   | ^^^---^^
+        //   | |  |
+        //   | |  something about `foo`
+        //   | something about `fn foo()`
+        annotations_position.sort_by(|a, b| {
+            fn len(a: Annotation) -> usize {
+                // Account for usize underflows
+                if a.end_col > a.start_col {
+                    a.end_col - a.start_col
                 } else {
-                    buffer.putc(line_offset + index,
-                                width_offset + annotation.start_col,
-                                '|',
-                                Style::UnderlineSecondary);
+                    a.start_col - a.end_col
                 }
-                draw_col_separator(buffer, line_offset + index, width_offset - 2);
             }
+            // Decreasing order
+            len(a.1).cmp(&len(b.1)).reverse()
+        });
 
-            if annotation.is_primary {
-                buffer.puts(line_offset + blank_lines,
-                            width_offset + annotation.start_col,
-                            annotation.label.as_ref().unwrap(),
-                            Style::LabelPrimary);
+        // Write the underlines.
+        //
+        // After this we will have:
+        //
+        // 2 |   fn foo() {
+        //   |  ____-_____^ starting here...
+        //   | |    |
+        //   | |    something about `foo`
+        // 3 | |
+        // 4 | | }
+        //   | |_^  ...ending here: test
+        for &(_, annotation) in &annotations_position {
+            let (underline, style) = if annotation.is_primary {
+                ('^', Style::UnderlinePrimary)
             } else {
-                buffer.puts(line_offset + blank_lines,
-                            width_offset + annotation.start_col,
-                            annotation.label.as_ref().unwrap(),
-                            Style::LabelSecondary);
+                ('-', Style::UnderlineSecondary)
+            };
+            for p in annotation.start_col..annotation.end_col {
+                buffer.putc(line_offset + 1,
+                            code_offset + p,
+                            underline,
+                            style);
             }
-            draw_col_separator(buffer, line_offset + blank_lines, width_offset - 2);
         }
     }
 
@@ -577,7 +828,8 @@ impl EmitterWriter {
                 self.render_source_line(&mut buffer,
                                         annotated_file.file.clone(),
                                         &annotated_file.lines[line_idx],
-                                        3 + max_line_num_len);
+                                        3 + max_line_num_len,
+                                        annotated_file.multiline_depth);
 
                 // check to see if we need to print out or elide lines that come between
                 // this annotated line and the next one
@@ -729,16 +981,38 @@ fn draw_col_separator(buffer: &mut StyledBuffer, line: usize, col: usize) {
 }
 
 fn draw_col_separator_no_space(buffer: &mut StyledBuffer, line: usize, col: usize) {
-    buffer.puts(line, col, "|", Style::LineNumber);
+    draw_col_separator_no_space_with_style(buffer, line, col, Style::LineNumber);
+}
+
+fn draw_col_separator_no_space_with_style(buffer: &mut StyledBuffer,
+                                          line: usize,
+                                          col: usize,
+                                          style: Style) {
+    buffer.putc(line, col, '|', style);
+}
+
+fn draw_range(buffer: &mut StyledBuffer, symbol: char, line: usize,
+              col_from: usize, col_to: usize, style: Style) {
+    for col in col_from..col_to {
+        buffer.putc(line, col, symbol, style);
+    }
 }
 
 fn draw_note_separator(buffer: &mut StyledBuffer, line: usize, col: usize) {
     buffer.puts(line, col, "= ", Style::LineNumber);
 }
 
+fn num_overlap(a_start: usize, a_end: usize, b_start: usize, b_end:usize, inclusive: bool) -> bool {
+    let extra = if inclusive {
+        1
+    } else {
+        0
+    };
+    (b_start..b_end + extra).contains(a_start) ||
+    (a_start..a_end + extra).contains(b_start)
+}
 fn overlaps(a1: &Annotation, a2: &Annotation) -> bool {
-    (a2.start_col..a2.end_col).contains(a1.start_col) ||
-    (a1.start_col..a1.end_col).contains(a2.start_col)
+    num_overlap(a1.start_col, a1.end_col, a2.start_col, a2.end_col, false)
 }
 
 fn emit_to_destination(rendered_buffer: &Vec<Vec<StyledString>>,
diff --git a/src/librustc_errors/snippet.rs b/src/librustc_errors/snippet.rs
index abfb71c861b..3bf428af994 100644
--- a/src/librustc_errors/snippet.rs
+++ b/src/librustc_errors/snippet.rs
@@ -42,6 +42,57 @@ pub struct Line {
 }
 
 #[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
+pub enum AnnotationType {
+    /// Annotation under a single line of code
+    Singleline,
+
+    /// Annotation under the first character of a multiline span
+    Minimized,
+
+    /// Annotation enclosing the first and last character of a multiline span
+    Multiline {
+        depth: usize,
+        line_start: usize,
+        line_end: usize,
+    },
+
+    // The Multiline type above is replaced with the following three in order
+    // to reuse the current label drawing code.
+    //
+    // Each of these corresponds to one part of the following diagram:
+    //
+    //     x |   foo(1 + bar(x,
+    //       |  _________^ starting here...           < MultilineStart
+    //     x | |             y),                      < MultilineLine
+    //       | |______________^ ...ending here: label < MultilineEnd
+    //     x |       z);
+    /// Annotation marking the first character of a fully shown multiline span
+    MultilineStart(usize),
+    /// Annotation marking the last character of a fully shown multiline span
+    MultilineEnd(usize),
+    /// Line at the left enclosing the lines of a fully shown multiline span
+    MultilineLine(usize),
+}
+
+impl AnnotationType {
+    pub fn depth(&self) -> usize {
+        match self {
+            &AnnotationType::Multiline {depth, ..} |
+                &AnnotationType::MultilineStart(depth) |
+                &AnnotationType::MultilineLine(depth) |
+                &AnnotationType::MultilineEnd(depth) => depth,
+            _ => 0,
+        }
+    }
+
+    pub fn increase_depth(&mut self) {
+        if let AnnotationType::Multiline {ref mut depth, ..} = *self {
+            *depth += 1;
+        }
+    }
+}
+
+#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
 pub struct Annotation {
     /// Start column, 0-based indexing -- counting *characters*, not
     /// utf-8 bytes. Note that it is important that this field goes
@@ -55,11 +106,57 @@ pub struct Annotation {
     /// Is this annotation derived from primary span
     pub is_primary: bool,
 
-    /// Is this a large span minimized down to a smaller span
-    pub is_minimized: bool,
-
     /// Optional label to display adjacent to the annotation.
     pub label: Option<String>,
+
+    /// Is this a single line, multiline or multiline span minimized down to a
+    /// smaller span.
+    pub annotation_type: AnnotationType,
+}
+
+impl Annotation {
+    pub fn is_minimized(&self) -> bool {
+        match self.annotation_type {
+            AnnotationType::Minimized => true,
+            _ => false,
+        }
+    }
+
+    pub fn is_multiline(&self) -> bool {
+        match self.annotation_type {
+            AnnotationType::Multiline {..} |
+                AnnotationType::MultilineStart(_) |
+                AnnotationType::MultilineLine(_) |
+                AnnotationType::MultilineEnd(_) => true,
+            _ => false,
+        }
+    }
+
+    pub fn as_start(&self) -> Annotation {
+        let mut a = self.clone();
+        a.annotation_type = AnnotationType::MultilineStart(self.annotation_type.depth());
+        a.end_col = a.start_col + 1;
+        a.label = Some("starting here...".to_owned());
+        a
+    }
+
+    pub fn as_end(&self) -> Annotation {
+        let mut a = self.clone();
+        a.annotation_type = AnnotationType::MultilineEnd(self.annotation_type.depth());
+        a.start_col = a.end_col - 1;
+        a.label = match a.label {
+            Some(l) => Some(format!("...ending here: {}", l)),
+            None => Some("..ending here".to_owned()),
+        };
+        a
+    }
+
+    pub fn as_line(&self) -> Annotation {
+        let mut a = self.clone();
+        a.annotation_type = AnnotationType::MultilineLine(self.annotation_type.depth());
+        a.label = None;
+        a
+    }
 }
 
 #[derive(Debug)]