about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/libsyntax/codemap.rs218
-rw-r--r--src/libsyntax/errors/snippet/mod.rs821
-rw-r--r--src/libsyntax/errors/snippet/test.rs524
3 files changed, 1428 insertions, 135 deletions
diff --git a/src/libsyntax/codemap.rs b/src/libsyntax/codemap.rs
index 35aa827782d..228af27f4b1 100644
--- a/src/libsyntax/codemap.rs
+++ b/src/libsyntax/codemap.rs
@@ -32,8 +32,6 @@ use serialize::{Encodable, Decodable, Encoder, Decoder};
 
 use ast::Name;
 
-use errors::emitter::MAX_HIGHLIGHT_LINES;
-
 // _____________________________________________________________________________
 // Pos, BytePos, CharPos
 //
@@ -51,7 +49,7 @@ pub struct BytePos(pub u32);
 /// A character offset. Because of multibyte utf8 characters, a byte offset
 /// is not equivalent to a character offset. The CodeMap will convert BytePos
 /// values to CharPos values as necessary.
-#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Debug)]
+#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
 pub struct CharPos(pub usize);
 
 // FIXME: Lots of boilerplate in these impls, but so far my attempts to fix
@@ -132,13 +130,29 @@ pub struct Span {
     pub expn_id: ExpnId
 }
 
-/// Spans are converted to MultiSpans just before error reporting, either automatically,
-/// generated by line grouping, or manually constructed.
-/// In the latter case care should be taken to ensure that spans are ordered, disjoint,
-/// and point into the same FileMap.
+/// A collection of spans. Spans have two orthogonal attributes:
+///
+/// - they can be *primary spans*. In this case they are the locus of
+///   the error, and would be rendered with `^^^`.
+/// - they can have a *label*. In this case, the label is written next
+///   to the mark in the snippet when we render.
 #[derive(Clone)]
 pub struct MultiSpan {
-    pub spans: Vec<Span>
+    primary_spans: Vec<Span>,
+    span_labels: Vec<(Span, String)>,
+}
+
+#[derive(Clone, Debug)]
+pub struct SpanLabel {
+    /// the span we are going to include in the final snippet
+    pub span: Span,
+
+    /// is this a primary span? This is the "locus" of the message,
+    /// and is indicated with a `^^^^` underline, versus `----`
+    pub is_primary: bool,
+
+    /// what label should we attach to this span (if any)?
+    pub label: Option<String>,
 }
 
 pub const DUMMY_SP: Span = Span { lo: BytePos(0), hi: BytePos(0), expn_id: NO_EXPANSION };
@@ -276,97 +290,76 @@ pub fn original_sp(cm: &CodeMap, sp: Span, enclosing_sp: Span) -> Span {
 
 impl MultiSpan {
     pub fn new() -> MultiSpan {
-        MultiSpan { spans: Vec::new() }
+        MultiSpan {
+            primary_spans: vec![],
+            span_labels: vec![]
+        }
     }
 
-    pub fn to_span_bounds(&self) -> Span {
-        assert!(!self.spans.is_empty());
-        let Span { lo, expn_id, .. } = *self.spans.first().unwrap();
-        let Span { hi, .. } = *self.spans.last().unwrap();
-        Span { lo: lo, hi: hi, expn_id: expn_id }
+    pub fn from_span(primary_span: Span) -> MultiSpan {
+        MultiSpan {
+            primary_spans: vec![primary_span],
+            span_labels: vec![]
+        }
     }
 
-    /// Merges or inserts the given span into itself.
-    pub fn push_merge(&mut self, mut sp: Span) {
-        let mut idx_merged = None;
-
-        for idx in 0.. {
-            let cur = match self.spans.get(idx) {
-                Some(s) => *s,
-                None => break,
-            };
-            // Try to merge with a contained Span
-            if let Some(union) = cur.merge(sp) {
-                self.spans[idx] = union;
-                sp = union;
-                idx_merged = Some(idx);
-                break;
-            }
-            // Or insert into the first sorted position
-            if sp.hi <= cur.lo {
-                self.spans.insert(idx, sp);
-                idx_merged = Some(idx);
-                break;
-            }
-        }
-        if let Some(idx) = idx_merged {
-            // Merge with spans trailing the insertion/merging position
-            while (idx + 1) < self.spans.len() {
-                if let Some(union) = self.spans[idx + 1].merge(sp) {
-                    self.spans[idx] = union;
-                    self.spans.remove(idx + 1);
-                } else {
-                    break;
-                }
-            }
-        } else {
-            self.spans.push(sp);
+    pub fn from_spans(vec: Vec<Span>) -> MultiSpan {
+        MultiSpan {
+            primary_spans: vec,
+            span_labels: vec![]
         }
     }
 
-    /// Inserts the given span into itself, for use with `end_highlight_lines`.
-    pub fn push_trim(&mut self, mut sp: Span) {
-        let mut prev = mk_sp(BytePos(0), BytePos(0));
+    pub fn push_primary_span(&mut self, span: Span) {
+        self.primary_spans.push(span);
+    }
 
-        if let Some(first) = self.spans.get_mut(0) {
-            if first.lo > sp.lo {
-                // Prevent us here from spanning fewer lines
-                // because of trimming the start of the span
-                // (this should not be visible, because this method ought
-                // to not be used in conjunction with `highlight_lines`)
-                first.lo = sp.lo;
-            }
+    pub fn push_span_label(&mut self, span: Span, label: String) {
+        self.span_labels.push((span, label));
+    }
+
+    /// Selects the first primary span (if any)
+    pub fn primary_span(&self) -> Option<Span> {
+        self.primary_spans.first().cloned()
+    }
+
+    /// Returns all primary spans.
+    pub fn primary_spans(&self) -> &[Span] {
+        &self.primary_spans
+    }
+
+    /// Returns the strings to highlight. If we have an explicit set,
+    /// return those, otherwise just give back an (unlabeled) version
+    /// of the primary span.
+    pub fn span_labels(&self) -> Vec<SpanLabel> {
+        let is_primary = |span| self.primary_spans.contains(&span);
+        let mut span_labels = vec![];
+
+        for &(span, ref label) in &self.span_labels {
+            span_labels.push(SpanLabel {
+                span: span,
+                is_primary: is_primary(span),
+                label: Some(label.clone())
+            });
         }
 
-        for idx in 0.. {
-            if let Some(sp_trim) = sp.trim_start(prev) {
-                // Implies `sp.hi > prev.hi`
-                let cur = match self.spans.get(idx) {
-                    Some(s) => *s,
-                    None => {
-                        sp = sp_trim;
-                        break;
-                    }
-                };
-                // `cur` may overlap with `sp_trim`
-                if let Some(cur_trim) = cur.trim_start(sp_trim) {
-                    // Implies `sp.hi < cur.hi`
-                    self.spans.insert(idx, sp_trim);
-                    self.spans[idx + 1] = cur_trim;
-                    return;
-                } else if sp.hi == cur.hi {
-                    return;
-                }
-                prev = cur;
+        for &span in &self.primary_spans {
+            if !span_labels.iter().any(|sl| sl.span == span) {
+                span_labels.push(SpanLabel {
+                    span: span,
+                    is_primary: true,
+                    label: None
+                });
             }
         }
-        self.spans.push(sp);
+
+        span_labels
     }
 }
 
 impl From<Span> for MultiSpan {
     fn from(span: Span) -> MultiSpan {
-        MultiSpan { spans: vec![span] }
+        MultiSpan::from_span(span)
     }
 }
 
@@ -929,6 +922,10 @@ impl CodeMap {
     }
 
     pub fn span_to_string(&self, sp: Span) -> String {
+        if sp == COMMAND_LINE_SP {
+            return "<command line option>".to_string();
+        }
+
         if self.files.borrow().is_empty() && sp.source_equal(&DUMMY_SP) {
             return "no-location".to_string();
         }
@@ -1099,12 +1096,16 @@ impl CodeMap {
     }
 
     pub fn span_to_lines(&self, sp: Span) -> FileLinesResult {
+        debug!("span_to_lines(sp={:?})", sp);
+
         if sp.lo > sp.hi {
             return Err(SpanLinesError::IllFormedSpan(sp));
         }
 
         let lo = self.lookup_char_pos(sp.lo);
+        debug!("span_to_lines: lo={:?}", lo);
         let hi = self.lookup_char_pos(sp.hi);
+        debug!("span_to_lines: hi={:?}", hi);
 
         if lo.file.start_pos != hi.file.start_pos {
             return Err(SpanLinesError::DistinctSources(DistinctSources {
@@ -1184,59 +1185,6 @@ impl CodeMap {
         }
     }
 
-    /// Groups and sorts spans by lines into `MultiSpan`s, where `push` adds them to their group,
-    /// specifying the unification behaviour for overlapping spans.
-    /// Spans overflowing a line are put into their own one-element-group.
-    pub fn custom_group_spans<F>(&self, mut spans: Vec<Span>, push: F) -> Vec<MultiSpan>
-        where F: Fn(&mut MultiSpan, Span)
-    {
-        spans.sort_by(|a, b| a.lo.cmp(&b.lo));
-        let mut groups = Vec::<MultiSpan>::new();
-        let mut overflowing = vec![];
-        let mut prev_expn = ExpnId(!2u32);
-        let mut prev_file = !0usize;
-        let mut prev_line = !0usize;
-        let mut err_size = 0;
-
-        for sp in spans {
-            let line = self.lookup_char_pos(sp.lo).line;
-            let line_hi = self.lookup_char_pos(sp.hi).line;
-            if line != line_hi {
-                overflowing.push(sp.into());
-                continue
-            }
-            let file = self.lookup_filemap_idx(sp.lo);
-
-            if err_size < MAX_HIGHLIGHT_LINES && sp.expn_id == prev_expn && file == prev_file {
-                // `push` takes care of sorting, trimming, and merging
-                push(&mut groups.last_mut().unwrap(), sp);
-                if line != prev_line {
-                    err_size += 1;
-                }
-            } else {
-                groups.push(sp.into());
-                err_size = 1;
-            }
-            prev_expn = sp.expn_id;
-            prev_file = file;
-            prev_line = line;
-        }
-        groups.extend(overflowing);
-        groups
-    }
-
-    /// Groups and sorts spans by lines into `MultiSpan`s, merging overlapping spans.
-    /// Spans overflowing a line are put into their own one-element-group.
-    pub fn group_spans(&self, spans: Vec<Span>) -> Vec<MultiSpan> {
-        self.custom_group_spans(spans, |msp, sp| msp.push_merge(sp))
-    }
-
-    /// Like `group_spans`, but trims overlapping spans instead of
-    /// merging them (for use with `end_highlight_lines`)
-    pub fn end_group_spans(&self, spans: Vec<Span>) -> Vec<MultiSpan> {
-        self.custom_group_spans(spans, |msp, sp| msp.push_trim(sp))
-    }
-
     pub fn get_filemap(&self, filename: &str) -> Rc<FileMap> {
         for fm in self.files.borrow().iter() {
             if filename == fm.name {
diff --git a/src/libsyntax/errors/snippet/mod.rs b/src/libsyntax/errors/snippet/mod.rs
new file mode 100644
index 00000000000..cd8f705ab2e
--- /dev/null
+++ b/src/libsyntax/errors/snippet/mod.rs
@@ -0,0 +1,821 @@
+// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Code for annotating snippets.
+
+use codemap::{CharPos, CodeMap, FileMap, LineInfo, Span};
+use std::cmp;
+use std::rc::Rc;
+use std::mem;
+use std::ops::Range;
+
+#[cfg(test)]
+mod test;
+
+pub struct SnippetData {
+    codemap: Rc<CodeMap>,
+    files: Vec<FileInfo>,
+}
+
+pub struct FileInfo {
+    file: Rc<FileMap>,
+
+    /// The "primary file", if any, gets a `-->` marker instead of
+    /// `>>>`, and has a line-number/column printed and not just a
+    /// filename.  It appears first in the listing. It is known to
+    /// contain at least one primary span, though primary spans (which
+    /// are designated with `^^^`) may also occur in other files.
+    primary_span: Option<Span>,
+
+    lines: Vec<Line>,
+}
+
+struct Line {
+    line_index: usize,
+    annotations: Vec<Annotation>,
+}
+
+#[derive(Clone, PartialOrd, Ord, PartialEq, Eq)]
+struct Annotation {
+    /// Start column, 0-based indexing -- counting *characters*, not
+    /// utf-8 bytes. Note that it is important that this field goes
+    /// first, so that when we sort, we sort orderings by start
+    /// column.
+    start_col: usize,
+
+    /// 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<String>,
+}
+
+#[derive(Debug)]
+pub struct RenderedLine {
+    pub text: Vec<StyledString>,
+    pub kind: RenderedLineKind,
+}
+
+#[derive(Debug)]
+pub struct StyledString {
+    pub text: String,
+    pub style: Style,
+}
+
+#[derive(Debug)]
+pub struct StyledBuffer {
+    text: Vec<Vec<char>>,
+    styles: Vec<Vec<Style>>
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum Style {
+    FileNameStyle,
+    LineAndColumn,
+    LineNumber,
+    Quotation,
+    UnderlinePrimary,
+    UnderlineSecondary,
+    LabelPrimary,
+    LabelSecondary,
+    NoStyle,
+}
+use self::Style::*;
+
+#[derive(Debug, Clone)]
+pub enum RenderedLineKind {
+    PrimaryFileName,
+    OtherFileName,
+    SourceText {
+        file: Rc<FileMap>,
+        line_index: usize,
+    },
+    Annotations,
+    Elision,
+}
+use self::RenderedLineKind::*;
+
+impl SnippetData {
+    pub fn new(codemap: Rc<CodeMap>,
+               primary_span: Option<Span>) // (*)
+               -> Self {
+        // (*) The primary span indicates the file that must appear
+        // first, and which will have a line number etc in its
+        // name. Outside of tests, this is always `Some`, but for many
+        // tests it's not relevant to test this portion of the logic,
+        // and it's tedious to pick a primary span (read: tedious to
+        // port older tests that predate the existence of a primary
+        // span).
+
+        debug!("SnippetData::new(primary_span={:?})", primary_span);
+
+        let mut data = SnippetData {
+            codemap: codemap.clone(),
+            files: vec![]
+        };
+        if let Some(primary_span) = primary_span {
+            let lo = codemap.lookup_char_pos(primary_span.lo);
+            data.files.push(
+                FileInfo {
+                    file: lo.file,
+                    primary_span: Some(primary_span),
+                    lines: vec![],
+                });
+        }
+        data
+    }
+
+    pub fn push(&mut self, span: Span, is_primary: bool, label: Option<String>) {
+        debug!("SnippetData::push(span={:?}, is_primary={}, label={:?})",
+               span, is_primary, label);
+
+        let file_lines = match self.codemap.span_to_lines(span) {
+            Ok(file_lines) => file_lines,
+            Err(_) => {
+                // ignore unprintable spans completely.
+                return;
+            }
+        };
+
+        self.file(&file_lines.file)
+            .push_lines(&file_lines.lines, is_primary, label);
+    }
+
+    fn file(&mut self, file_map: &Rc<FileMap>) -> &mut FileInfo {
+        let index = self.files.iter().position(|f| f.file.name == file_map.name);
+        if let Some(index) = index {
+            return &mut self.files[index];
+        }
+
+        self.files.push(
+            FileInfo {
+                file: file_map.clone(),
+                lines: vec![],
+                primary_span: None,
+            });
+        self.files.last_mut().unwrap()
+    }
+
+    pub fn render_lines(&self) -> Vec<RenderedLine> {
+        debug!("SnippetData::render_lines()");
+
+        let mut rendered_lines: Vec<_> =
+            self.files.iter()
+                      .flat_map(|f| f.render_file_lines(&self.codemap))
+                      .collect();
+        prepend_prefixes(&mut rendered_lines);
+        trim_lines(&mut rendered_lines);
+        rendered_lines
+    }
+}
+
+pub trait StringSource {
+    fn make_string(self) -> String;
+}
+
+impl StringSource for String {
+    fn make_string(self) -> String {
+        self
+    }
+}
+
+impl StringSource for Vec<char> {
+    fn make_string(self) -> String {
+        self.into_iter().collect()
+    }
+}
+
+impl<S> From<(S, Style, RenderedLineKind)> for RenderedLine
+    where S: StringSource
+{
+    fn from((text, style, kind): (S, Style, RenderedLineKind)) -> Self {
+        RenderedLine {
+            text: vec![StyledString {
+                text: text.make_string(),
+                style: style,
+            }],
+            kind: kind,
+        }
+    }
+}
+
+impl<S1,S2> From<(S1, Style, S2, Style, RenderedLineKind)> for RenderedLine
+    where S1: StringSource, S2: StringSource
+{
+    fn from(tuple: (S1, Style, S2, Style, RenderedLineKind))
+            -> Self {
+        let (text1, style1, text2, style2, kind) = tuple;
+        RenderedLine {
+            text: vec![
+                StyledString {
+                    text: text1.make_string(),
+                    style: style1,
+                },
+                StyledString {
+                    text: text2.make_string(),
+                    style: style2,
+                }
+            ],
+            kind: kind,
+        }
+    }
+}
+
+impl RenderedLine {
+    fn trim_last(&mut self) {
+        if !self.text.is_empty() {
+            let last_text = &mut self.text.last_mut().unwrap().text;
+            let len = last_text.trim_right().len();
+            last_text.truncate(len);
+        }
+    }
+}
+
+impl RenderedLineKind {
+    fn prefix(&self) -> StyledString {
+        match *self {
+            SourceText { file: _, line_index } =>
+                StyledString {
+                    text: format!("{}", line_index + 1),
+                    style: LineNumber,
+                },
+            Elision =>
+                StyledString {
+                    text: String::from("..."),
+                    style: LineNumber,
+                },
+            PrimaryFileName |
+            OtherFileName |
+            Annotations =>
+                StyledString {
+                    text: String::from(""),
+                    style: LineNumber,
+                },
+        }
+    }
+}
+
+impl StyledBuffer {
+    fn new() -> StyledBuffer {
+        StyledBuffer { text: vec![], styles: vec![] }
+    }
+
+    fn render(&self, source_kind: RenderedLineKind) -> Vec<RenderedLine> {
+        let mut output: Vec<RenderedLine> = vec![];
+        let mut styled_vec: Vec<StyledString> = 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],
+                  is_primary: bool,
+                  label: Option<String>) {
+        assert!(lines.len() > 0);
+
+        // 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.
+        let (line0, remaining_lines) = lines.split_first().unwrap();
+        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);
+            }
+        }
+    }
+
+    /// Ensure that we have a `Line` struct corresponding to
+    /// `line_index` in the file. If we already have some other lines,
+    /// then this will add the intervening lines to ensure that we
+    /// have a complete snippet. (Note that when we finally display,
+    /// some of those lines may be elided.)
+    fn ensure_source_line(&mut self, line_index: usize) -> usize {
+        if self.lines.is_empty() {
+            self.lines.push(Line::new(line_index));
+            return 0;
+        }
+
+        // Find the range of lines we have thus far.
+        let first_line_index = self.lines.first().unwrap().line_index;
+        let last_line_index = self.lines.last().unwrap().line_index;
+        assert!(first_line_index <= last_line_index);
+
+        // If the new line is lower than all the lines we have thus
+        // far, then insert the new line and any intervening lines at
+        // the front. In a silly attempt at micro-optimization, we
+        // don't just call `insert` repeatedly, but instead make a new
+        // (empty) vector, pushing the new lines onto it, and then
+        // appending the old vector.
+        if line_index < first_line_index {
+            let lines = mem::replace(&mut self.lines, vec![]);
+            self.lines.extend(
+                (line_index .. first_line_index)
+                    .map(|line| Line::new(line))
+                    .chain(lines));
+            return 0;
+        }
+
+        // If the new line comes after the ones we have so far, insert
+        // lines for it.
+        if line_index > last_line_index {
+            self.lines.extend(
+                (last_line_index+1 .. line_index+1)
+                    .map(|line| Line::new(line)));
+            return self.lines.len() - 1;
+        }
+
+        // Otherwise it should already exist.
+        return line_index - first_line_index;
+    }
+
+    fn render_file_lines(&self, codemap: &Rc<CodeMap>) -> Vec<RenderedLine> {
+        // Group our lines by those with annotations and those without
+        let mut lines_iter = self.lines.iter().peekable();
+
+        let mut line_groups = vec![];
+
+        loop {
+            match lines_iter.next() {
+                None => break,
+                Some(line) if line.annotations.is_empty() => {
+                    // Collect unannotated group
+                    let mut unannotated_group : Vec<&Line> = vec![];
+
+                    unannotated_group.push(line);
+
+                    loop {
+                        let next_line =
+                            match lines_iter.peek() {
+                                None => break,
+                                Some(x) if !x.annotations.is_empty() => break,
+                                Some(x) => x.clone()
+                            };
+
+                        unannotated_group.push(next_line);
+                        lines_iter.next();
+                    }
+
+                    line_groups.push((false, unannotated_group));
+                }
+                Some(line) => {
+                    // Collect annotated group
+                    let mut annotated_group : Vec<&Line> = vec![];
+
+                    annotated_group.push(line);
+
+                    loop {
+                        let next_line =
+                            match lines_iter.peek() {
+                                None => break,
+                                Some(x) if x.annotations.is_empty() => break,
+                                Some(x) => x.clone()
+                            };
+
+                        annotated_group.push(next_line);
+                        lines_iter.next();
+                    }
+
+                    line_groups.push((true, annotated_group));
+                }
+            }
+        }
+
+        let mut output = vec![];
+
+        // First insert the name of the file.
+        match self.primary_span {
+            Some(span) => {
+                let lo = codemap.lookup_char_pos(span.lo);
+                output.push(RenderedLine {
+                    text: vec![StyledString {
+                        text: lo.file.name.clone(),
+                        style: FileNameStyle,
+                    }, StyledString {
+                        text: format!(":{}:{}", lo.line, lo.col.0 + 1),
+                        style: LineAndColumn,
+                    }],
+                    kind: PrimaryFileName,
+                });
+            }
+            None => {
+                output.push(RenderedLine {
+                    text: vec![StyledString {
+                        text: self.file.name.clone(),
+                        style: FileNameStyle,
+                    }],
+                    kind: OtherFileName,
+                });
+            }
+        }
+
+        for &(is_annotated, ref group) in line_groups.iter() {
+            if is_annotated {
+                let mut annotation_ends_at_eol = false;
+                let mut prev_ends_at_eol = false;
+                let mut elide_unlabeled_region = false;
+
+                for group_line in group.iter() {
+                    let source_string_len =
+                        self.file.get_line(group_line.line_index)
+                                 .map(|s| s.len())
+                                 .unwrap_or(0);
+
+                    for annotation in &group_line.annotations {
+                        if annotation.end_col == source_string_len {
+                            annotation_ends_at_eol = true;
+                        }
+                    }
+
+                    let is_single_unlabeled_annotated_line =
+                        if group_line.annotations.len() == 1 {
+                            if let Some(annotation) = group_line.annotations.first() {
+                                match annotation.label {
+                                    Some(_) => false,
+                                    None => annotation.start_col == 0 &&
+                                            annotation.end_col == source_string_len
+                                }
+                            } else {
+                                false
+                            }
+                        } else {
+                            false
+                        };
+
+                    if prev_ends_at_eol && is_single_unlabeled_annotated_line {
+                        if !elide_unlabeled_region {
+                            output.push(RenderedLine::from((String::new(),
+                                NoStyle, Elision)));
+                            elide_unlabeled_region = true;
+                            prev_ends_at_eol = true;
+                        }
+                        continue;
+                    }
+
+                    let mut v = self.render_line(group_line);
+                    output.append(&mut v);
+
+                    prev_ends_at_eol = annotation_ends_at_eol;
+                }
+            } else {
+                if group.len() > 1 {
+                    output.push(RenderedLine::from((String::new(), NoStyle, Elision)));
+                } else {
+                    let mut v: Vec<RenderedLine> =
+                        group.iter().flat_map(|line| self.render_line(line)).collect();
+                    output.append(&mut v);
+                }
+            }
+        }
+
+        output
+    }
+
+    fn render_line(&self, line: &Line) -> Vec<RenderedLine> {
+        let source_string = self.file.get_line(line.line_index)
+                                     .unwrap_or("");
+        let source_kind = SourceText {
+            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 styled_buffer.render(source_kind);
+        }
+
+        // We want to display like this:
+        //
+        //      vec.push(vec.pop().unwrap());
+        //      ---      ^^^               _ previous borrow ends here
+        //      |        |
+        //      |        error occurs here
+        //      previous borrow of `vec` occurs here
+        //
+        // But there are some weird edge cases to be aware of:
+        //
+        //      vec.push(vec.pop().unwrap());
+        //      --------                    - previous borrow ends here
+        //      ||
+        //      |this makes no sense
+        //      previous borrow of `vec` occurs here
+        //
+        // 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);
+
+        // Sort the annotations by (start, end col)
+        let mut annotations = line.annotations.clone();
+        annotations.sort();
+
+        // Next, create the highlight line.
+        for annotation in &annotations {
+            for p in annotation.start_col .. annotation.end_col {
+                if annotation.is_primary {
+                    styled_buffer.putc(1, p, '^', UnderlinePrimary);
+                    styled_buffer.set_style(0, p, UnderlinePrimary);
+                } else {
+                    styled_buffer.putc(1, p, '-', UnderlineSecondary);
+                }
+            }
+        }
+
+        // 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 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
+        //
+        // But sometimes that's not possible because one of the other
+        // annotations overlaps it. For example, from the test
+        // `span_overlap_label`, we have the following annotations
+        // (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
+        //      |
+        //      fn_span
+        //
+        // which is totally weird. Instead we want:
+        //
+        //      fn foo(x: u32) {
+        //      --------------
+        //      |      |
+        //      |      x_span
+        //      fn_span
+        //
+        // which is...less weird, at least. In fact, in general, if
+        // 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 {
+                        styled_buffer.append(1, &highlight_label, LabelPrimary);
+                    } else {
+                        styled_buffer.append(1, &highlight_label, LabelSecondary);
+                    }
+                    labeled_annotations = previous;
+                }
+            }
+        }
+
+        // If that's the last annotation, we're done
+        if labeled_annotations.is_empty() {
+            return styled_buffer.render(source_kind);
+        }
+
+        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 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 {
+                    styled_buffer.putc(index, annotation.start_col, '|', UnderlinePrimary);
+                } else {
+                    styled_buffer.putc(index, annotation.start_col, '|', UnderlineSecondary);
+                }
+            }
+
+            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);
+            }
+        }
+
+        styled_buffer.render(source_kind)
+    }
+}
+
+fn prepend_prefixes(rendered_lines: &mut [RenderedLine]) {
+    let prefixes: Vec<_> =
+        rendered_lines.iter()
+                      .map(|rl| rl.kind.prefix())
+                      .collect();
+
+    // find the max amount of spacing we need; add 1 to
+    // p.text.len() to leave space between the prefix and the
+    // source text
+    let padding_len =
+        prefixes.iter()
+                .map(|p| if p.text.len() == 0 { 0 } else { p.text.len() + 1 })
+                .max()
+                .unwrap_or(0);
+
+    // Ensure we insert at least one character of padding, so that the
+    // `-->` arrows can fit etc.
+    let padding_len = cmp::max(padding_len, 1);
+
+    for (mut prefix, line) in prefixes.into_iter().zip(rendered_lines) {
+        let extra_spaces = (prefix.text.len() .. padding_len).map(|_| ' ');
+        prefix.text.extend(extra_spaces);
+        match line.kind {
+            RenderedLineKind::Elision => {
+                line.text.insert(0, prefix);
+            }
+            RenderedLineKind::PrimaryFileName => {
+                //   --> filename
+                // 22 |>
+                //   ^
+                //   padding_len
+                let dashes = (0..padding_len - 1).map(|_| ' ')
+                                                 .chain(Some('-'))
+                                                 .chain(Some('-'))
+                                                 .chain(Some('>'))
+                                                 .chain(Some(' '));
+                line.text.insert(0, StyledString {text: dashes.collect(),
+                                                  style: LineNumber})
+            }
+            RenderedLineKind::OtherFileName => {
+                // >>>>> filename
+                // 22 |>
+                //   ^
+                //   padding_len
+                let dashes = (0..padding_len + 2).map(|_| '>')
+                                                 .chain(Some(' '));
+                line.text.insert(0, StyledString {text: dashes.collect(),
+                                                  style: LineNumber})
+            }
+            _ => {
+                line.text.insert(0, prefix);
+                line.text.insert(1, StyledString {text: String::from("|> "),
+                                                  style: LineNumber})
+            }
+        }
+    }
+}
+
+fn trim_lines(rendered_lines: &mut [RenderedLine]) {
+    for line in rendered_lines {
+        while !line.text.is_empty() {
+            line.trim_last();
+            if line.text.last().unwrap().text.is_empty() {
+                line.text.pop();
+            } else {
+                break;
+            }
+        }
+    }
+}
+
+impl Line {
+    fn new(line_index: usize) -> Line {
+        Line {
+            line_index: line_index,
+            annotations: vec![]
+        }
+    }
+
+    fn push_annotation(&mut self,
+                       start: CharPos,
+                       end: CharPos,
+                       is_primary: bool,
+                       label: Option<String>) {
+        self.annotations.push(Annotation {
+            start_col: start.0,
+            end_col: end.0,
+            is_primary: is_primary,
+            label: label,
+        });
+    }
+}
+
+fn overlaps(a1: &Annotation,
+            a2: &Annotation)
+            -> bool
+{
+    between(a1.start_col, a2.start_col .. a2.end_col) ||
+        between(a2.start_col, a1.start_col .. a1.end_col)
+}
+
+fn between(v: usize, range: Range<usize>) -> bool {
+    v >= range.start && v < range.end
+}
diff --git a/src/libsyntax/errors/snippet/test.rs b/src/libsyntax/errors/snippet/test.rs
new file mode 100644
index 00000000000..44ece285b1b
--- /dev/null
+++ b/src/libsyntax/errors/snippet/test.rs
@@ -0,0 +1,524 @@
+// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Code for testing annotated snippets.
+
+use codemap::{BytePos, CodeMap, FileMap, NO_EXPANSION, Span};
+use std::rc::Rc;
+use super::{RenderedLine, SnippetData};
+
+/// Returns the span corresponding to the `n`th occurrence of
+/// `substring` in `source_text`.
+trait CodeMapExtension {
+    fn span_substr(&self,
+                   file: &Rc<FileMap>,
+                   source_text: &str,
+                   substring: &str,
+                   n: usize)
+                   -> Span;
+}
+
+impl CodeMapExtension for CodeMap {
+    fn span_substr(&self,
+                   file: &Rc<FileMap>,
+                   source_text: &str,
+                   substring: &str,
+                   n: usize)
+                   -> Span
+    {
+        println!("span_substr(file={:?}/{:?}, substring={:?}, n={})",
+                 file.name, file.start_pos, substring, n);
+        let mut i = 0;
+        let mut hi = 0;
+        loop {
+            let offset = source_text[hi..].find(substring).unwrap_or_else(|| {
+                panic!("source_text `{}` does not have {} occurrences of `{}`, only {}",
+                       source_text, n, substring, i);
+            });
+            let lo = hi + offset;
+            hi = lo + substring.len();
+            if i == n {
+                let span = Span {
+                    lo: BytePos(lo as u32 + file.start_pos.0),
+                    hi: BytePos(hi as u32 + file.start_pos.0),
+                    expn_id: NO_EXPANSION,
+                };
+                assert_eq!(&self.span_to_snippet(span).unwrap()[..],
+                           substring);
+                return span;
+            }
+            i += 1;
+        }
+    }
+}
+
+fn splice(start: Span, end: Span) -> Span {
+    Span {
+        lo: start.lo,
+        hi: end.hi,
+        expn_id: NO_EXPANSION,
+    }
+}
+
+fn make_string(lines: &[RenderedLine]) -> String {
+    lines.iter()
+         .flat_map(|rl| {
+             rl.text.iter()
+                    .map(|s| &s.text[..])
+                    .chain(Some("\n"))
+         })
+         .collect()
+}
+
+#[test]
+fn one_line() {
+    let file_text = r#"
+fn foo() {
+    vec.push(vec.pop().unwrap());
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+    let span_vec0 = cm.span_substr(&foo, file_text, "vec", 0);
+    let span_vec1 = cm.span_substr(&foo, file_text, "vec", 1);
+    let span_semi = cm.span_substr(&foo, file_text, ";", 0);
+
+    let mut snippet = SnippetData::new(cm, None);
+    snippet.push(span_vec0, false, Some(format!("previous borrow of `vec` occurs here")));
+    snippet.push(span_vec1, false, Some(format!("error occurs here")));
+    snippet.push(span_semi, false, Some(format!("previous borrow ends here")));
+
+    let lines = snippet.render_lines();
+    println!("{:#?}", lines);
+
+    let text: String = make_string(&lines);
+
+    println!("text=\n{}", text);
+    assert_eq!(&text[..], &r#"
+>>>> foo.rs
+3 |>     vec.push(vec.pop().unwrap());
+  |>     ---      ---                - previous borrow ends here
+  |>     |        |
+  |>     |        error occurs here
+  |>     previous borrow of `vec` occurs here
+"#[1..]);
+}
+
+#[test]
+fn two_files() {
+    let file_text_foo = r#"
+fn foo() {
+    vec.push(vec.pop().unwrap());
+}
+"#;
+
+    let file_text_bar = r#"
+fn bar() {
+    // these blank links here
+    // serve to ensure that the line numbers
+    // from bar.rs
+    // require more digits
+
+
+
+
+
+
+
+
+
+
+    vec.push();
+
+    // this line will get elided
+
+    vec.pop().unwrap());
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo_map = cm.new_filemap_and_lines("foo.rs", file_text_foo);
+    let span_foo_vec0 = cm.span_substr(&foo_map, file_text_foo, "vec", 0);
+    let span_foo_vec1 = cm.span_substr(&foo_map, file_text_foo, "vec", 1);
+    let span_foo_semi = cm.span_substr(&foo_map, file_text_foo, ";", 0);
+
+    let bar_map = cm.new_filemap_and_lines("bar.rs", file_text_bar);
+    let span_bar_vec0 = cm.span_substr(&bar_map, file_text_bar, "vec", 0);
+    let span_bar_vec1 = cm.span_substr(&bar_map, file_text_bar, "vec", 1);
+    let span_bar_semi = cm.span_substr(&bar_map, file_text_bar, ";", 0);
+
+    let mut snippet = SnippetData::new(cm, Some(span_foo_vec1));
+    snippet.push(span_foo_vec0, false, Some(format!("a")));
+    snippet.push(span_foo_vec1, true, Some(format!("b")));
+    snippet.push(span_foo_semi, false, Some(format!("c")));
+    snippet.push(span_bar_vec0, false, Some(format!("d")));
+    snippet.push(span_bar_vec1, false, Some(format!("e")));
+    snippet.push(span_bar_semi, false, Some(format!("f")));
+
+    let lines = snippet.render_lines();
+    println!("{:#?}", lines);
+
+    let text: String = make_string(&lines);
+
+    println!("text=\n{}", text);
+
+    // Note that the `|>` remain aligned across both files:
+    assert_eq!(&text[..], &r#"
+   --> foo.rs:3:14
+3   |>     vec.push(vec.pop().unwrap());
+    |>     ---      ^^^                - c
+    |>     |        |
+    |>     |        b
+    |>     a
+>>>>>> bar.rs
+17  |>     vec.push();
+    |>     ---       - f
+    |>     |
+    |>     d
+...
+21  |>     vec.pop().unwrap());
+    |>     --- e
+"#[1..]);
+}
+
+#[test]
+fn multi_line() {
+    let file_text = r#"
+fn foo() {
+    let name = find_id(&data, 22).unwrap();
+
+    // Add one more item we forgot to the vector. Silly us.
+    data.push(Data { name: format!("Hera"), id: 66 });
+
+    // Print everything out.
+    println!("Name: {:?}", name);
+    println!("Data: {:?}", data);
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+    let span_data0 = cm.span_substr(&foo, file_text, "data", 0);
+    let span_data1 = cm.span_substr(&foo, file_text, "data", 1);
+    let span_rbrace = cm.span_substr(&foo, file_text, "}", 3);
+
+    let mut snippet = SnippetData::new(cm, None);
+    snippet.push(span_data0, false, Some(format!("immutable borrow begins here")));
+    snippet.push(span_data1, false, Some(format!("mutable borrow occurs here")));
+    snippet.push(span_rbrace, false, Some(format!("immutable borrow ends here")));
+
+    let lines = snippet.render_lines();
+    println!("{:#?}", lines);
+
+    let text: String = make_string(&lines);
+
+    println!("text=\n{}", text);
+    assert_eq!(&text[..], &r#"
+>>>>>> foo.rs
+3   |>     let name = find_id(&data, 22).unwrap();
+    |>                         ---- immutable borrow begins here
+...
+6   |>     data.push(Data { name: format!("Hera"), id: 66 });
+    |>     ---- mutable borrow occurs here
+...
+11  |> }
+    |> - immutable borrow ends here
+"#[1..]);
+}
+
+#[test]
+fn overlapping() {
+    let file_text = r#"
+fn foo() {
+    vec.push(vec.pop().unwrap());
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+    let span0 = cm.span_substr(&foo, file_text, "vec.push", 0);
+    let span1 = cm.span_substr(&foo, file_text, "vec", 0);
+    let span2 = cm.span_substr(&foo, file_text, "ec.push", 0);
+    let span3 = cm.span_substr(&foo, file_text, "unwrap", 0);
+
+    let mut snippet = SnippetData::new(cm, None);
+    snippet.push(span0, false, Some(format!("A")));
+    snippet.push(span1, false, Some(format!("B")));
+    snippet.push(span2, false, Some(format!("C")));
+    snippet.push(span3, false, Some(format!("D")));
+
+    let lines = snippet.render_lines();
+    println!("{:#?}", lines);
+    let text: String = make_string(&lines);
+
+    println!("text=r#\"\n{}\".trim_left()", text);
+    assert_eq!(&text[..], &r#"
+>>>> foo.rs
+3 |>     vec.push(vec.pop().unwrap());
+  |>     --------           ------ D
+  |>     ||
+  |>     |C
+  |>     A
+  |>     B
+"#[1..]);
+}
+
+#[test]
+fn one_line_out_of_order() {
+    let file_text = r#"
+fn foo() {
+    vec.push(vec.pop().unwrap());
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+    let span_vec0 = cm.span_substr(&foo, file_text, "vec", 0);
+    let span_vec1 = cm.span_substr(&foo, file_text, "vec", 1);
+    let span_semi = cm.span_substr(&foo, file_text, ";", 0);
+
+    // intentionally don't push the snippets left to right
+    let mut snippet = SnippetData::new(cm, None);
+    snippet.push(span_vec1, false, Some(format!("error occurs here")));
+    snippet.push(span_vec0, false, Some(format!("previous borrow of `vec` occurs here")));
+    snippet.push(span_semi, false, Some(format!("previous borrow ends here")));
+
+    let lines = snippet.render_lines();
+    println!("{:#?}", lines);
+    let text: String = make_string(&lines);
+
+    println!("text=r#\"\n{}\".trim_left()", text);
+    assert_eq!(&text[..], &r#"
+>>>> foo.rs
+3 |>     vec.push(vec.pop().unwrap());
+  |>     ---      ---                - previous borrow ends here
+  |>     |        |
+  |>     |        error occurs here
+  |>     previous borrow of `vec` occurs here
+"#[1..]);
+}
+
+#[test]
+fn elide_unnecessary_lines() {
+    let file_text = r#"
+fn foo() {
+    let mut vec = vec![0, 1, 2];
+    let mut vec2 = vec;
+    vec2.push(3);
+    vec2.push(4);
+    vec2.push(5);
+    vec2.push(6);
+    vec.push(7);
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+    let span_vec0 = cm.span_substr(&foo, file_text, "vec", 3);
+    let span_vec1 = cm.span_substr(&foo, file_text, "vec", 8);
+
+    let mut snippet = SnippetData::new(cm, None);
+    snippet.push(span_vec0, false, Some(format!("`vec` moved here because it \
+        has type `collections::vec::Vec<i32>`")));
+    snippet.push(span_vec1, false, Some(format!("use of moved value: `vec`")));
+
+    let lines = snippet.render_lines();
+    println!("{:#?}", lines);
+    let text: String = make_string(&lines);
+    println!("text=r#\"\n{}\".trim_left()", text);
+    assert_eq!(&text[..], &r#"
+>>>>>> foo.rs
+4   |>     let mut vec2 = vec;
+    |>                    --- `vec` moved here because it has type `collections::vec::Vec<i32>`
+...
+9   |>     vec.push(7);
+    |>     --- use of moved value: `vec`
+"#[1..]);
+}
+
+#[test]
+fn spans_without_labels() {
+    let file_text = r#"
+fn foo() {
+    let mut vec = vec![0, 1, 2];
+    let mut vec2 = vec;
+    vec2.push(3);
+    vec2.push(4);
+    vec2.push(5);
+    vec2.push(6);
+    vec.push(7);
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+
+    let mut snippet = SnippetData::new(cm.clone(), None);
+    for i in 0..4 {
+        let span_veci = cm.span_substr(&foo, file_text, "vec", i);
+        snippet.push(span_veci, false, None);
+    }
+
+    let lines = snippet.render_lines();
+    let text: String = make_string(&lines);
+    println!("text=&r#\"\n{}\n\"#[1..]", text);
+    assert_eq!(text, &r#"
+>>>> foo.rs
+3 |>     let mut vec = vec![0, 1, 2];
+  |>             ---   ---
+4 |>     let mut vec2 = vec;
+  |>             ---    ---
+"#[1..]);
+}
+
+#[test]
+fn span_long_selection() {
+    let file_text = r#"
+impl SomeTrait for () {
+    fn foo(x: u32) {
+        // impl 1
+        // impl 2
+        // impl 3
+    }
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+
+    let mut snippet = SnippetData::new(cm.clone(), None);
+    let fn_span = cm.span_substr(&foo, file_text, "fn", 0);
+    let rbrace_span = cm.span_substr(&foo, file_text, "}", 0);
+    snippet.push(splice(fn_span, rbrace_span), false, None);
+    let lines = snippet.render_lines();
+    let text: String = make_string(&lines);
+    println!("r#\"\n{}\"", text);
+    assert_eq!(text, &r#"
+>>>>>> foo.rs
+3   |>     fn foo(x: u32) {
+    |>     ----------------
+...
+"#[1..]);
+}
+
+#[test]
+fn span_overlap_label() {
+    // Test that we don't put `x_span` to the right of its highlight,
+    // since there is another highlight that overlaps it.
+
+    let file_text = r#"
+    fn foo(x: u32) {
+    }
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+
+    let mut snippet = SnippetData::new(cm.clone(), None);
+    let fn_span = cm.span_substr(&foo, file_text, "fn foo(x: u32)", 0);
+    let x_span = cm.span_substr(&foo, file_text, "x", 0);
+    snippet.push(fn_span, false, Some(format!("fn_span")));
+    snippet.push(x_span, false, Some(format!("x_span")));
+    let lines = snippet.render_lines();
+    let text: String = make_string(&lines);
+    println!("r#\"\n{}\"", text);
+    assert_eq!(text, &r#"
+>>>> foo.rs
+2 |>     fn foo(x: u32) {
+  |>     --------------
+  |>     |      |
+  |>     |      x_span
+  |>     fn_span
+"#[1..]);
+}
+
+#[test]
+fn span_overlap_label2() {
+    // Test that we don't put `x_span` to the right of its highlight,
+    // since there is another highlight that overlaps it. In this
+    // case, the overlap is only at the beginning, but it's still
+    // better to show the beginning more clearly.
+
+    let file_text = r#"
+    fn foo(x: u32) {
+    }
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+
+    let mut snippet = SnippetData::new(cm.clone(), None);
+    let fn_span = cm.span_substr(&foo, file_text, "fn foo(x", 0);
+    let x_span = cm.span_substr(&foo, file_text, "x: u32)", 0);
+    snippet.push(fn_span, false, Some(format!("fn_span")));
+    snippet.push(x_span, false, Some(format!("x_span")));
+    let lines = snippet.render_lines();
+    let text: String = make_string(&lines);
+    println!("r#\"\n{}\"", text);
+    assert_eq!(text, &r#"
+>>>> foo.rs
+2 |>     fn foo(x: u32) {
+  |>     --------------
+  |>     |      |
+  |>     |      x_span
+  |>     fn_span
+"#[1..]);
+}
+
+#[test]
+fn span_overlap_label3() {
+    // Test that we don't put `x_span` to the right of its highlight,
+    // since there is another highlight that overlaps it. In this
+    // case, the overlap is only at the beginning, but it's still
+    // better to show the beginning more clearly.
+
+    let file_text = r#"
+    fn foo() {
+       let closure = || {
+           inner
+       };
+    }
+}
+"#;
+
+    let cm = Rc::new(CodeMap::new());
+    let foo = cm.new_filemap_and_lines("foo.rs", file_text);
+
+    let mut snippet = SnippetData::new(cm.clone(), None);
+
+    let closure_span = {
+        let closure_start_span = cm.span_substr(&foo, file_text, "||", 0);
+        let closure_end_span = cm.span_substr(&foo, file_text, "}", 0);
+        splice(closure_start_span, closure_end_span)
+    };
+
+    let inner_span = cm.span_substr(&foo, file_text, "inner", 0);
+
+    snippet.push(closure_span, false, Some(format!("foo")));
+    snippet.push(inner_span, false, Some(format!("bar")));
+
+    let lines = snippet.render_lines();
+    let text: String = make_string(&lines);
+    println!("r#\"\n{}\"", text);
+    assert_eq!(text, &r#"
+>>>> foo.rs
+3 |>        let closure = || {
+  |>                      ---- foo
+4 |>            inner
+  |> ----------------
+  |>            |
+  |>            bar
+5 |>        };
+  |> --------
+"#[1..]);
+}