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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use crate::termcolor::{ColorChoice, StandardStream, WriteColor};
use crate::*;
use codespan::files::Error;
use colored::*;
use file::Files;
use std::collections::HashSet;
use std::io;

/// A trait describing a struct which can render diagnostics to a writer such as stderr.
///
/// Each formatter may rely on behavior specific to a batch of diagnostics, therefore
/// you should collect all diagnostics and then call the appropriate formatter
pub trait Formatter {
    fn emit_stdout(&mut self, diagnostics: &[Diagnostic], files: &dyn Files) -> io::Result<()> {
        let stderr = StandardStream::stderr(ColorChoice::Always);
        let mut out = stderr.lock();
        self.emit_with_writer(diagnostics, files, &mut out)
    }

    fn emit_stderr(&mut self, diagnostics: &[Diagnostic], files: &dyn Files) -> io::Result<()> {
        let stderr = StandardStream::stderr(ColorChoice::Always);
        let mut out = stderr.lock();
        self.emit_with_writer(diagnostics, files, &mut out)
    }

    fn emit_with_writer(
        &mut self,
        diagnostics: &[Diagnostic],
        files: &dyn Files,
        writer: &mut dyn WriteColor,
    ) -> io::Result<()>;
}

#[derive(Debug, Copy, Clone)]
pub struct ShortFormatter;

impl Formatter for ShortFormatter {
    fn emit_with_writer(
        &mut self,
        diagnostics: &[Diagnostic],
        files: &dyn Files,
        writer: &mut dyn WriteColor,
    ) -> io::Result<()> {
        let mut ids = HashSet::new();
        diagnostics.iter().for_each(|d| {
            ids.insert(d.file_id);
        });
        for id in ids {
            let cur_diags = diagnostics
                .iter()
                .filter(|x| x.file_id == id && x.primary.is_some());
            if cur_diags.clone().count() == 0 {
                continue;
            }

            let name = files.name(id).expect("Invalid file id");
            writeln!(writer, "{}", name.white().underline())?;
            let mut line_starts = vec![];

            for diag in cur_diags.clone() {
                let line_index = files
                    .line_index(id, diag.primary.as_ref().unwrap().span.range.start)
                    .expect("Line index out of bounds");
                let line_span = files.line_range(id, line_index).unwrap();
                let column = diag.primary.as_ref().unwrap().span.range.start - line_span.start;
                line_starts.push((line_index, column));
            }
            let max_msg_len = cur_diags
                .clone()
                .max_by_key(|x| x.title.trim().len())
                .map(|x| x.title.trim().len())
                .unwrap();

            let max_severity_len = cur_diags
                .clone()
                .map(|x| format!("{:?}", x.severity).len())
                .max()
                .unwrap();

            let max_loc = line_starts
                .iter()
                .map(|x| x.0.to_string().len() + x.1.to_string().len() + 1)
                .max()
                .unwrap();
            for (diag, (line, column)) in cur_diags.zip(line_starts) {
                write!(writer, "  ")?;
                write!(
                    writer,
                    "{} ",
                    " ".repeat(max_loc - (line.to_string().len() + column.to_string().len() + 1))
                )?;
                write!(
                    writer,
                    "{}{}{}  ",
                    line.to_string().truecolor(140, 140, 140),
                    ":".truecolor(140, 140, 140),
                    column.to_string().truecolor(140, 140, 140)
                )?;
                let color = match diag.severity {
                    Severity::Bug | Severity::Error => Color::BrightRed,
                    Severity::Note => Color::BrightCyan,
                    Severity::Warning => Color::BrightYellow,
                    Severity::Help => Color::BrightGreen,
                };
                let severity_string = format!("{:?}", diag.severity).to_ascii_lowercase();
                write!(
                    writer,
                    "{}{}  ",
                    " ".repeat(max_severity_len - severity_string.len()),
                    severity_string.color(color)
                )?;
                write!(
                    writer,
                    "{}{}  ",
                    diag.title.trim(),
                    " ".repeat(max_msg_len - diag.title.trim().len())
                )?;
                if let Some(code) = diag.code.clone() {
                    write!(writer, "{}", code.white())?;
                }
                writeln!(writer)?;
            }
            writeln!(writer)?;
        }
        Ok(())
    }
}

#[derive(Debug, Copy, Clone)]
pub struct LongFormatter;

impl Formatter for LongFormatter {
    fn emit_with_writer(
        &mut self,
        diagnostics: &[Diagnostic],
        files: &dyn Files,
        writer: &mut dyn WriteColor,
    ) -> io::Result<()> {
        for diag in diagnostics {
            match Emitter::new(files).emit_with_writer(diag, writer) {
                Ok(_) => {}
                Err(err) => {
                    if let Error::Io(io_err) = err {
                        return Err(io_err);
                    } else {
                        panic!("{}", err)
                    }
                }
            }
        }
        Ok(())
    }
}