1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
use codespan::CodeMap;
use std::{fmt, io};
use termcolor::{Color, ColorSpec, WriteColor};

use {Diagnostic, LabelStyle};

struct Pad<T>(T, usize);

impl<T: fmt::Display> fmt::Display for Pad<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for _ in 0..(self.1) {
            self.0.fmt(f)?;
        }
        Ok(())
    }
}

pub fn emit<W>(mut writer: W, codemap: &CodeMap, diagnostic: &Diagnostic) -> io::Result<()>
where
    W: WriteColor,
{
    let supports_color = writer.supports_color();
    let line_location_color = ColorSpec::new()
        // Blue is really difficult to see on the standard windows command line
        .set_fg(Some(if cfg!(windows) { Color::Cyan } else { Color::Blue }))
        .clone();
    let diagnostic_color = ColorSpec::new()
        .set_fg(Some(diagnostic.severity.color()))
        .clone();

    let highlight_color = ColorSpec::new().set_bold(true).set_intense(true).clone();

    writer.set_color(&highlight_color
        .clone()
        .set_fg(Some(diagnostic.severity.color())))?;
    write!(writer, "{}", diagnostic.severity)?;
    writer.set_color(&highlight_color)?;
    writeln!(writer, ": {}", diagnostic.message)?;
    writer.reset()?;

    for label in &diagnostic.labels {
        match codemap.find_file(label.span.start()) {
            None => if let Some(ref message) = label.message {
                writeln!(writer, "- {}", message)?
            },
            Some(file) => {
                let (line, column) = file.location(label.span.start()).expect("location");
                writeln!(
                    writer,
                    "- {file}:{line}:{column}",
                    file = file.name(),
                    line = line.number(),
                    column = column.number(),
                )?;

                let line_span = file.line_span(line).expect("line_span");

                let line_prefix = file.src_slice(line_span.with_end(label.span.start()))
                    .expect("line_prefix");
                let line_marked = file.src_slice(label.span).expect("line_marked");
                let line_suffix = file.src_slice(line_span.with_start(label.span.end()))
                    .expect("line_suffix")
                    .trim_right_matches(|ch: char| ch == '\r' || ch == '\n');

                let mark = match label.style {
                    LabelStyle::Primary => '^',
                    LabelStyle::Secondary => '-',
                };
                let label_color = match label.style {
                    LabelStyle::Primary => diagnostic_color.clone(),
                    LabelStyle::Secondary => ColorSpec::new()
                        .set_fg(Some(if cfg!(windows) {
                            Color::Cyan
                        } else {
                            Color::Blue
                        }))
                        .clone(),
                };

                writer.set_color(&line_location_color)?;
                let line_string = line.number().to_string();
                let line_location_prefix = format!("{} | ", Pad(' ', line_string.len()));
                write!(writer, "{} | ", line_string)?;
                writer.reset()?;

                write!(writer, "{}", line_prefix)?;
                writer.set_color(&label_color)?;
                write!(writer, "{}", line_marked)?;
                writer.reset()?;
                writeln!(writer, "{}", line_suffix)?;

                if !supports_color || label.message.is_some() {
                    writer.set_color(&line_location_color)?;
                    write!(writer, "{}", line_location_prefix)?;
                    writer.reset()?;

                    writer.set_color(&label_color)?;
                    write!(
                        writer,
                        "{}{}",
                        Pad(' ', line_prefix.len()),
                        Pad(mark, line_marked.len()),
                    )?;
                    writer.reset()?;

                    if label.message.is_none() {
                        writeln!(writer)?;
                    }
                }

                match label.message {
                    None => (),
                    Some(ref label) => {
                        writer.set_color(&label_color)?;
                        writeln!(writer, " {}", label)?;
                        writer.reset()?;
                    }
                }
            }
        }
    }
    Ok(())
}