use std::{cmp::Ordering, sync::Arc};
use super::{style::DiagnosticStyle, Component};
use crate::errors::ComponentFormatError;
use compiler_base_span::{span_to_filename_string, SourceFile, SourceMap, Span};
use rustc_errors::styled_buffer::{StyledBuffer, StyledString};
use rustc_span::LineInfo;
const CODE_LINE_PREFIX: &str = " | ";
const FILE_PATH_PREFIX: &str = "-->";
pub enum Label {
Error(String),
Warning(String),
Note,
Help,
}
impl Component<DiagnosticStyle> for Label {
fn format(&self, sb: &mut StyledBuffer<DiagnosticStyle>, _: &mut Vec<ComponentFormatError>) {
let (text, style, code) = match self {
Label::Error(ecode) => ("error", DiagnosticStyle::NeedFix, Some(ecode)),
Label::Warning(wcode) => ("warning", DiagnosticStyle::NeedAttention, Some(wcode)),
Label::Note => ("note", DiagnosticStyle::Important, None),
Label::Help => ("help", DiagnosticStyle::Helpful, None),
};
sb.appendl(text, Some(style));
if let Some(c) = code {
sb.appendl("[", Some(DiagnosticStyle::Helpful));
sb.appendl(c.as_str(), Some(DiagnosticStyle::Helpful));
sb.appendl("]", Some(DiagnosticStyle::Helpful));
}
}
}
impl Component<DiagnosticStyle> for StyledString<DiagnosticStyle> {
#[inline]
fn format(&self, sb: &mut StyledBuffer<DiagnosticStyle>, _: &mut Vec<ComponentFormatError>) {
sb.appendl(&self.text, self.style);
}
}
pub struct UnderLine {
start: usize,
end: usize,
symbol: StyledString<DiagnosticStyle>,
}
const DEFAULT_UNDERLINE_LABEL: &str = "^";
impl UnderLine {
#[inline]
pub fn new_with_default_label(
start: usize,
end: usize,
style: Option<DiagnosticStyle>,
) -> Self {
Self {
start,
end,
symbol: StyledString::<DiagnosticStyle>::new(
DEFAULT_UNDERLINE_LABEL.to_string(),
style,
),
}
}
#[inline]
pub fn new(start: usize, end: usize, label: String, style: Option<DiagnosticStyle>) -> Self {
Self {
start,
end,
symbol: StyledString::<DiagnosticStyle>::new(label, style),
}
}
}
impl Component<DiagnosticStyle> for UnderLine {
fn format(&self, sb: &mut StyledBuffer<DiagnosticStyle>, errs: &mut Vec<ComponentFormatError>) {
match self.start.cmp(&self.end) {
Ordering::Greater => errs.push(ComponentFormatError::new(
"UnderLine",
"Failed to Format UnderLine in One Line.",
)),
Ordering::Less => {
let indent = self.start;
format!("{:<indent$}", "").format(sb, errs);
for _ in self.start..self.end {
self.symbol.format(sb, errs);
}
}
Ordering::Equal => {}
}
}
}
pub struct CodeSnippet {
code_span: Span,
source_map: Arc<SourceMap>,
}
impl CodeSnippet {
#[inline]
pub fn new(code_span: Span, source_map: Arc<SourceMap>) -> Self {
Self {
code_span,
source_map,
}
}
}
impl Component<DiagnosticStyle> for CodeSnippet {
fn format(&self, sb: &mut StyledBuffer<DiagnosticStyle>, errs: &mut Vec<ComponentFormatError>) {
match self.source_map.span_to_lines(self.code_span) {
Ok(affected_lines) => {
match self
.source_map
.source_file_by_filename(&span_to_filename_string(
&self.code_span,
&self.source_map,
)) {
Some(sf) => {
if let Some(line) = affected_lines.lines.first() {
let indent = (line.line_index + 1).to_string().len();
self.format_file_info(sb, errs, &affected_lines.lines, indent);
StyledString::new(
format!("{:<indent$}{}\n", "", CODE_LINE_PREFIX),
Some(DiagnosticStyle::Url),
)
.format(sb, errs);
self.format_code_line(sb, errs, line, indent, &sf)
}
}
None => errs.push(ComponentFormatError::new(
"CodeSnippet",
"Failed to Load Source File",
)),
};
}
Err(_) => errs.push(ComponentFormatError::new(
"CodeSnippet",
"Failed to Display Code Snippet Lines",
)),
};
}
}
impl CodeSnippet {
fn format_code_line(
&self,
sb: &mut StyledBuffer<DiagnosticStyle>,
errs: &mut Vec<ComponentFormatError>,
line: &LineInfo,
indent: usize,
sf: &SourceFile,
) {
let line_index = (line.line_index + 1).to_string();
StyledString::new(
format!("{:<indent$}{}", line_index, CODE_LINE_PREFIX),
Some(DiagnosticStyle::Url),
)
.format(sb, errs);
if let Some(line) = sf.get_line(line.line_index) {
sb.appendl(&line, None);
} else {
errs.push(ComponentFormatError::new(
"CodeSnippet",
"Failed to Display Code Snippet.",
))
}
sb.appendl("\n", None);
StyledString::new(
format!("{:<indent$}{}", "", CODE_LINE_PREFIX),
Some(DiagnosticStyle::Url),
)
.format(sb, errs);
UnderLine::new_with_default_label(
line.start_col.0,
line.end_col.0,
Some(DiagnosticStyle::NeedFix),
)
.format(sb, errs);
}
fn format_file_info(
&self,
sb: &mut StyledBuffer<DiagnosticStyle>,
errs: &mut Vec<ComponentFormatError>,
lines: &[LineInfo],
indent: usize,
) {
let (first_line, first_col) = match lines.first() {
Some(line) => (line.line_index + 1, line.start_col.0 + 1),
None => {
errs.push(ComponentFormatError::new(
"CodeSnippet",
"Failed to Display Code Snippet.",
));
(0, 0)
}
};
StyledString::new(
format!("{:>indent$}{}", "", FILE_PATH_PREFIX),
Some(DiagnosticStyle::Url),
)
.format(sb, errs);
StyledString::new(
format!(
" {}:{}:{}\n",
span_to_filename_string(&self.code_span, &self.source_map),
first_line,
first_col
),
Some(DiagnosticStyle::Url),
)
.format(sb, errs);
}
}