]> git.lizzy.rs Git - rust.git/blob - src/libsyntax/errors/snippet/mod.rs
Better handling of tab in error
[rust.git] / src / libsyntax / errors / snippet / mod.rs
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.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 // Code for annotating snippets.
12
13 use codemap::{CharPos, CodeMap, FileMap, LineInfo, Span};
14 use errors::check_old_skool;
15 use std::cmp;
16 use std::rc::Rc;
17 use std::mem;
18
19 mod test;
20
21 #[derive(Clone)]
22 pub struct SnippetData {
23     codemap: Rc<CodeMap>,
24     files: Vec<FileInfo>,
25 }
26
27 #[derive(Clone)]
28 pub struct FileInfo {
29     file: Rc<FileMap>,
30
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>,
37
38     lines: Vec<Line>,
39 }
40
41 #[derive(Clone, Debug)]
42 struct Line {
43     line_index: usize,
44     annotations: Vec<Annotation>,
45 }
46
47 #[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
48 struct Annotation {
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
52     /// column.
53     start_col: usize,
54
55     /// End column within the line (exclusive)
56     end_col: usize,
57
58     /// Is this annotation derived from primary span
59     is_primary: bool,
60
61     /// Optional label to display adjacent to the annotation.
62     label: Option<String>,
63 }
64
65 #[derive(Debug)]
66 pub struct RenderedLine {
67     pub text: Vec<StyledString>,
68     pub kind: RenderedLineKind,
69 }
70
71 #[derive(Debug)]
72 pub struct StyledString {
73     pub text: String,
74     pub style: Style,
75 }
76
77 #[derive(Debug)]
78 pub struct StyledBuffer {
79     text: Vec<Vec<char>>,
80     styles: Vec<Vec<Style>>
81 }
82
83 #[derive(Copy, Clone, Debug, PartialEq)]
84 pub enum Style {
85     FileNameStyle,
86     LineAndColumn,
87     LineNumber,
88     Quotation,
89     UnderlinePrimary,
90     UnderlineSecondary,
91     LabelPrimary,
92     LabelSecondary,
93     NoStyle,
94 }
95
96 #[derive(Debug, Clone)]
97 pub enum RenderedLineKind {
98     PrimaryFileName,
99     OtherFileName,
100     SourceText {
101         file: Rc<FileMap>,
102         line_index: usize,
103     },
104     Annotations,
105     Elision,
106 }
107
108 impl SnippetData {
109     pub fn new(codemap: Rc<CodeMap>,
110                primary_span: Option<Span>) // (*)
111                -> Self {
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
118         // span).
119
120         debug!("SnippetData::new(primary_span={:?})", primary_span);
121
122         let mut data = SnippetData {
123             codemap: codemap.clone(),
124             files: vec![]
125         };
126         if let Some(primary_span) = primary_span {
127             let lo = codemap.lookup_char_pos(primary_span.lo);
128             data.files.push(
129                 FileInfo {
130                     file: lo.file,
131                     primary_span: Some(primary_span),
132                     lines: vec![],
133                 });
134         }
135         data
136     }
137
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);
141
142         let file_lines = match self.codemap.span_to_lines(span) {
143             Ok(file_lines) => file_lines,
144             Err(_) => {
145                 // ignore unprintable spans completely.
146                 return;
147             }
148         };
149
150         self.file(&file_lines.file)
151             .push_lines(&file_lines.lines, is_primary, label);
152     }
153
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];
158         }
159
160         self.files.push(
161             FileInfo {
162                 file: file_map.clone(),
163                 lines: vec![],
164                 primary_span: None,
165             });
166         self.files.last_mut().unwrap()
167     }
168
169     pub fn render_lines(&self) -> Vec<RenderedLine> {
170         debug!("SnippetData::render_lines()");
171
172         let mut rendered_lines: Vec<_> =
173             self.files.iter()
174                       .flat_map(|f| f.render_file_lines(&self.codemap))
175                       .collect();
176         prepend_prefixes(&mut rendered_lines);
177         trim_lines(&mut rendered_lines);
178         rendered_lines
179     }
180 }
181
182 pub trait StringSource {
183     fn make_string(self) -> String;
184 }
185
186 impl StringSource for String {
187     fn make_string(self) -> String {
188         self
189     }
190 }
191
192 impl StringSource for Vec<char> {
193     fn make_string(self) -> String {
194         self.into_iter().collect()
195     }
196 }
197
198 impl<S> From<(S, Style, RenderedLineKind)> for RenderedLine
199     where S: StringSource
200 {
201     fn from((text, style, kind): (S, Style, RenderedLineKind)) -> Self {
202         RenderedLine {
203             text: vec![StyledString {
204                 text: text.make_string(),
205                 style: style,
206             }],
207             kind: kind,
208         }
209     }
210 }
211
212 impl<S1,S2> From<(S1, Style, S2, Style, RenderedLineKind)> for RenderedLine
213     where S1: StringSource, S2: StringSource
214 {
215     fn from(tuple: (S1, Style, S2, Style, RenderedLineKind)) -> Self {
216         let (text1, style1, text2, style2, kind) = tuple;
217         RenderedLine {
218             text: vec![
219                 StyledString {
220                     text: text1.make_string(),
221                     style: style1,
222                 },
223                 StyledString {
224                     text: text2.make_string(),
225                     style: style2,
226                 }
227             ],
228             kind: kind,
229         }
230     }
231 }
232
233 impl RenderedLine {
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);
238         }
239     }
240 }
241
242 impl RenderedLineKind {
243     fn prefix(&self) -> StyledString {
244         match *self {
245             RenderedLineKind::SourceText { file: _, line_index } =>
246                 StyledString {
247                     text: format!("{}", line_index + 1),
248                     style: Style::LineNumber,
249                 },
250             RenderedLineKind::Elision =>
251                 StyledString {
252                     text: String::from("..."),
253                     style: Style::LineNumber,
254                 },
255             RenderedLineKind::PrimaryFileName |
256             RenderedLineKind::OtherFileName |
257             RenderedLineKind::Annotations =>
258                 StyledString {
259                     text: String::from(""),
260                     style: Style::LineNumber,
261                 },
262         }
263     }
264 }
265
266 impl StyledBuffer {
267     fn new() -> StyledBuffer {
268         StyledBuffer { text: vec![], styles: vec![] }
269     }
270
271     fn render(&self, source_kind: RenderedLineKind) -> Vec<RenderedLine> {
272         let mut output: Vec<RenderedLine> = vec![];
273         let mut styled_vec: Vec<StyledString> = vec![];
274
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();
278
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 });
283                     }
284                     current_style = s;
285                     current_text = String::new();
286                 }
287                 current_text.push(c);
288             }
289             if !current_text.is_empty() {
290                 styled_vec.push(StyledString { text: current_text, style: current_style });
291             }
292
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() });
296             } else {
297                 output.push(RenderedLine { text: styled_vec, kind: RenderedLineKind::Annotations });
298             }
299             styled_vec = vec![];
300         }
301
302         output
303     }
304
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![]);
309         }
310
311         if col < self.text[line].len() {
312             self.text[line][col] = chr;
313             self.styles[line][col] = style;
314         } else {
315             let mut i = self.text[line].len();
316             while i < col {
317                 let s = match self.text[0].get(i) {
318                     Some(&'\t') => '\t',
319                     _ => ' '
320                 };
321                 self.text[line].push(s);
322                 self.styles[line].push(Style::NoStyle);
323                 i += 1;
324             }
325             self.text[line].push(chr);
326             self.styles[line].push(style);
327         }
328     }
329
330     fn puts(&mut self, line: usize, col: usize, string: &str, style: Style) {
331         let mut n = col;
332         for c in string.chars() {
333             self.putc(line, n, c, style);
334             n += 1;
335         }
336     }
337
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;
341         }
342     }
343
344     fn append(&mut self, line: usize, string: &str, style: Style) {
345         if line >= self.text.len() {
346             self.puts(line, 0, string, style);
347         } else {
348             let col = self.text[line].len();
349             self.puts(line, col, string, style);
350         }
351     }
352 }
353
354 impl FileInfo {
355     fn push_lines(&mut self,
356                   lines: &[LineInfo],
357                   is_primary: bool,
358                   label: Option<String>) {
359         assert!(lines.len() > 0);
360
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:
364         //
365         // ```
366         // --> foo.rs:2:1
367         // 2   |> fn conflicting_items<'grammar>(state: &LR0State<'grammar>)
368         //     |> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
369         // 3   |>                               -> Set<LR0Item<'grammar>>
370         //     |> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
371         // (and so on)
372         // ```
373         //
374         // we produce:
375         //
376         // ```
377         // --> foo.rs:2:1
378         // 2   |> fn conflicting_items<'grammar>(state: &LR0State<'grammar>)
379         //        ^
380         // ```
381         //
382         // Basically, although this loses information, multi-line spans just
383         // never look good.
384
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)
387         } else {
388             (lines[0].line_index, lines[0].start_col, CharPos(lines[0].start_col.0 + 1))
389         };
390
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 {
397             end_col.0 += 1;
398         }
399
400         let index = self.ensure_source_line(line);
401         self.lines[index].push_annotation(start_col,
402                                           end_col,
403                                           is_primary,
404                                           label);
405     }
406
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));
415             return 0;
416         }
417
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);
422
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![]);
431             self.lines.extend(
432                 (line_index .. first_line_index)
433                     .map(|line| Line::new(line))
434                     .chain(lines));
435             return 0;
436         }
437
438         // If the new line comes after the ones we have so far, insert
439         // lines for it.
440         if line_index > last_line_index {
441             self.lines.extend(
442                 (last_line_index+1 .. line_index+1)
443                     .map(|line| Line::new(line)));
444             return self.lines.len() - 1;
445         }
446
447         // Otherwise it should already exist.
448         return line_index - first_line_index;
449     }
450
451     fn render_file_lines(&self, codemap: &Rc<CodeMap>) -> Vec<RenderedLine> {
452         let old_school = check_old_skool();
453
454         // As a first step, we elide any instance of more than one
455         // continuous unannotated line.
456
457         let mut lines_iter = self.lines.iter();
458         let mut output = vec![];
459
460         // First insert the name of the file.
461         if !old_school {
462             match self.primary_span {
463                 Some(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,
469                         }, StyledString {
470                             text: format!(":{}:{}", lo.line, lo.col.0 + 1),
471                             style: Style::LineAndColumn,
472                         }],
473                         kind: RenderedLineKind::PrimaryFileName,
474                     });
475                 }
476                 None => {
477                     output.push(RenderedLine {
478                         text: vec![StyledString {
479                             text: self.file.name.clone(),
480                             style: Style::FileNameStyle,
481                         }],
482                         kind: RenderedLineKind::OtherFileName,
483                     });
484                 }
485             }
486         }
487
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; }
493
494                 let mut rendered_lines = self.render_line(line);
495                 assert!(!rendered_lines.is_empty());
496                 if old_school {
497                     match self.primary_span {
498                         Some(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,
503                             });
504                             rendered_lines[0].text.insert(0, StyledString {
505                                 text: lo.file.name.clone(),
506                                 style: Style::FileNameStyle,
507                             });
508                             let gap_amount =
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: {:?}",
513                                     line);
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
518                                 });
519                             }
520                         }
521                         _ =>()
522                     }
523                 }
524                 output.append(&mut rendered_lines);
525                 next_line = lines_iter.next();
526             }
527
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();
536             }
537             if unannotated_lines > 1 {
538                 output.push(RenderedLine::from((String::new(),
539                                                 Style::NoStyle,
540                                                 RenderedLineKind::Elision)));
541             } else if let Some(line) = unannotated_line {
542                 output.append(&mut self.render_line(line));
543             }
544         }
545
546         output
547     }
548
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)
552                                      .unwrap_or("");
553         let source_kind = RenderedLineKind::SourceText {
554             file: self.file.clone(),
555             line_index: line.line_index,
556         };
557
558         let mut styled_buffer = StyledBuffer::new();
559
560         // First create the source line we will highlight.
561         styled_buffer.append(0, &source_string, Style::Quotation);
562
563         if line.annotations.is_empty() {
564             return styled_buffer.render(source_kind);
565         }
566
567         // We want to display like this:
568         //
569         //      vec.push(vec.pop().unwrap());
570         //      ---      ^^^               _ previous borrow ends here
571         //      |        |
572         //      |        error occurs here
573         //      previous borrow of `vec` occurs here
574         //
575         // But there are some weird edge cases to be aware of:
576         //
577         //      vec.push(vec.pop().unwrap());
578         //      --------                    - previous borrow ends here
579         //      ||
580         //      |this makes no sense
581         //      previous borrow of `vec` occurs here
582         //
583         // For this reason, we group the lines into "highlight lines"
584         // and "annotations lines", where the highlight lines have the `~`.
585
586         //let mut highlight_line = Self::whitespace(&source_string);
587
588         // Sort the annotations by (start, end col)
589         let mut annotations = line.annotations.clone();
590         annotations.sort();
591
592         // Next, create the highlight line.
593         for annotation in &annotations {
594             if old_school {
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
600                             } else {
601                                 Style::UnderlineSecondary
602                             });
603                     }
604                     else {
605                         styled_buffer.putc(1, p, '~',
606                             if annotation.is_primary {
607                                 Style::UnderlinePrimary
608                             } else {
609                                 Style::UnderlineSecondary
610                             });
611                     }
612                 }
613             }
614             else {
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);
619                     } else {
620                         styled_buffer.putc(1, p, '-', Style::UnderlineSecondary);
621                     }
622                 }
623             }
624         }
625
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());
631
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);
635         }
636         if old_school {
637             return styled_buffer.render(source_kind);
638         }
639
640         // Now add the text labels. We try, when possible, to stick the rightmost
641         // annotation at the end of the highlight line:
642         //
643         //      vec.push(vec.pop().unwrap());
644         //      ---      ---               - previous borrow ends here
645         //
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):
650         //
651         //      fn foo(x: u32) {
652         //      --------------
653         //             -
654         //
655         // In this case, we can't stick the rightmost-most label on
656         // the highlight line, or we would get:
657         //
658         //      fn foo(x: u32) {
659         //      -------- x_span
660         //      |
661         //      fn_span
662         //
663         // which is totally weird. Instead we want:
664         //
665         //      fn foo(x: u32) {
666         //      --------------
667         //      |      |
668         //      |      x_span
669         //      fn_span
670         //
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) => {
678                 if previous.iter()
679                            .chain(&unlabeled_annotations)
680                            .all(|a| !overlaps(a, last))
681                 {
682                     // append the label afterwards; we keep it in a separate
683                     // string
684                     let highlight_label: String = format!(" {}", last.label.as_ref().unwrap());
685                     if last.is_primary {
686                         styled_buffer.append(1, &highlight_label, Style::LabelPrimary);
687                     } else {
688                         styled_buffer.append(1, &highlight_label, Style::LabelSecondary);
689                     }
690                     labeled_annotations = previous;
691                 }
692             }
693         }
694
695         // If that's the last annotation, we're done
696         if labeled_annotations.is_empty() {
697             return styled_buffer.render(source_kind);
698         }
699
700         for (index, annotation) in labeled_annotations.iter().enumerate() {
701             // Leave:
702             // - 1 extra line
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;
706
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);
712                 } else {
713                     styled_buffer.putc(index, annotation.start_col, '|', Style::UnderlineSecondary);
714                 }
715             }
716
717             if annotation.is_primary {
718                 styled_buffer.puts(blank_lines, annotation.start_col,
719                     annotation.label.as_ref().unwrap(), Style::LabelPrimary);
720             } else {
721                 styled_buffer.puts(blank_lines, annotation.start_col,
722                     annotation.label.as_ref().unwrap(), Style::LabelSecondary);
723             }
724         }
725
726         styled_buffer.render(source_kind)
727     }
728 }
729
730 fn prepend_prefixes(rendered_lines: &mut [RenderedLine]) {
731     let old_school = check_old_skool();
732     if old_school {
733         return;
734     }
735
736     let prefixes: Vec<_> =
737         rendered_lines.iter()
738                       .map(|rl| rl.kind.prefix())
739                       .collect();
740
741     // find the max amount of spacing we need; add 1 to
742     // p.text.len() to leave space between the prefix and the
743     // source text
744     let padding_len =
745         prefixes.iter()
746                 .map(|p| if p.text.len() == 0 { 0 } else { p.text.len() + 1 })
747                 .max()
748                 .unwrap_or(0);
749
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);
753
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);
757         match line.kind {
758             RenderedLineKind::Elision => {
759                 line.text.insert(0, prefix);
760             }
761             RenderedLineKind::PrimaryFileName => {
762                 //   --> filename
763                 // 22 |>
764                 //   ^
765                 //   padding_len
766                 let dashes = (0..padding_len - 1).map(|_| ' ')
767                                                  .chain(Some('-'))
768                                                  .chain(Some('-'))
769                                                  .chain(Some('>'))
770                                                  .chain(Some(' '));
771                 line.text.insert(0, StyledString {text: dashes.collect(),
772                                                   style: Style::LineNumber})
773             }
774             RenderedLineKind::OtherFileName => {
775                 //   ::: filename
776                 // 22 |>
777                 //   ^
778                 //   padding_len
779                 let dashes = (0..padding_len - 1).map(|_| ' ')
780                                                  .chain(Some(':'))
781                                                  .chain(Some(':'))
782                                                  .chain(Some(':'))
783                                                  .chain(Some(' '));
784                 line.text.insert(0, StyledString {text: dashes.collect(),
785                                                   style: Style::LineNumber})
786             }
787             _ => {
788                 line.text.insert(0, prefix);
789                 line.text.insert(1, StyledString {text: String::from("|> "),
790                                                   style: Style::LineNumber})
791             }
792         }
793     }
794 }
795
796 fn trim_lines(rendered_lines: &mut [RenderedLine]) {
797     for line in rendered_lines {
798         while !line.text.is_empty() {
799             line.trim_last();
800             if line.text.last().unwrap().text.is_empty() {
801                 line.text.pop();
802             } else {
803                 break;
804             }
805         }
806     }
807 }
808
809 impl Line {
810     fn new(line_index: usize) -> Line {
811         Line {
812             line_index: line_index,
813             annotations: vec![]
814         }
815     }
816
817     fn push_annotation(&mut self,
818                        start: CharPos,
819                        end: CharPos,
820                        is_primary: bool,
821                        label: Option<String>) {
822         self.annotations.push(Annotation {
823             start_col: start.0,
824             end_col: end.0,
825             is_primary: is_primary,
826             label: label,
827         });
828     }
829 }
830
831 fn overlaps(a1: &Annotation,
832             a2: &Annotation)
833             -> bool
834 {
835     (a2.start_col .. a2.end_col).contains(a1.start_col) ||
836         (a1.start_col .. a1.end_col).contains(a2.start_col)
837 }