use std::collections::HashMap;
use rsjsonnet_lang::span::{SpanId, SpanManager};
use super::TextPartKind;
use crate::src_manager::SrcManager;
pub(super) struct Message {
pub(super) kind: MessageKind,
pub(super) message: String,
pub(super) labels: Vec<MessageLabel>,
}
pub(super) enum MessageKind {
Note,
Error,
}
pub(super) struct MessageLabel {
pub(super) span: SpanId,
pub(super) kind: LabelKind,
pub(super) text: String,
}
#[derive(Copy, Clone)]
pub(super) enum LabelKind {
Note,
Error,
}
impl Message {
pub(super) fn render(
&self,
span_mgr: &SpanManager,
src_mgr: &SrcManager,
out: &mut Vec<(String, TextPartKind)>,
) {
match self.kind {
MessageKind::Note => {
put_note_header(&self.message, out);
}
MessageKind::Error => {
put_error_header(&self.message, out);
}
}
put_spans_and_labels(&self.labels, span_mgr, src_mgr, out);
}
}
pub(super) fn put_note_header<T: std::fmt::Display>(msg: T, out: &mut Vec<(String, TextPartKind)>) {
out.push(("note".into(), TextPartKind::NoteLabel));
out.push((": ".into(), TextPartKind::MainMessage));
out.push((msg.to_string(), TextPartKind::MainMessage));
out.push(("\n".into(), TextPartKind::Space));
}
pub(super) fn put_error_header<T: std::fmt::Display>(
msg: T,
out: &mut Vec<(String, TextPartKind)>,
) {
out.push(("error".into(), TextPartKind::ErrorLabel));
out.push((": ".into(), TextPartKind::MainMessage));
out.push((msg.to_string(), TextPartKind::MainMessage));
out.push(("\n".into(), TextPartKind::Space));
}
fn put_spans_and_labels(
labels: &[MessageLabel],
span_mgr: &SpanManager,
src_mgr: &SrcManager,
out: &mut Vec<(String, TextPartKind)>,
) {
struct SourceData<'a> {
annots: sourceannot::Annotations<'a, TextPartKind>,
first_span_line: usize,
first_span_col: usize,
}
let mut by_src = HashMap::new();
let mut src_order = Vec::new();
let main_style = get_main_style();
let margin_line = main_style.margin.unwrap().line_char;
let error_anot_style = get_error_annot_style();
let note_anot_style = get_note_annot_style();
for label in labels.iter() {
let (span_ctx, span_start, span_end) = span_mgr.get_span(label.span);
let rsjsonnet_lang::span::SpanContext::Source(src_id) = *span_mgr.get_context(span_ctx);
let snippet = src_mgr.get_file_snippet(src_id);
let (line, col) = snippet.src_pos_to_line_col(span_start);
by_src
.entry(src_id)
.or_insert_with(|| {
src_order.push(src_id);
SourceData {
annots: sourceannot::Annotations::new(snippet, &main_style),
first_span_line: line,
first_span_col: col,
}
})
.annots
.add_annotation(
span_start..span_end,
match label.kind {
LabelKind::Error => &error_anot_style,
LabelKind::Note => ¬e_anot_style,
},
vec![(
label.text.clone(),
match label.kind {
LabelKind::Error => TextPartKind::ErrorLabel,
LabelKind::Note => TextPartKind::NoteLabel,
},
)],
);
}
let max_line_no_width = by_src
.values()
.map(|data| data.annots.max_line_no_width())
.max()
.unwrap_or(0);
for &src_id in src_order.iter() {
struct Output<'a>(&'a mut Vec<(String, TextPartKind)>);
impl sourceannot::Output<TextPartKind> for Output<'_> {
type Error = std::convert::Infallible;
fn put_str(&mut self, text: &str, meta: &TextPartKind) -> Result<(), Self::Error> {
self.0.push((text.into(), *meta));
Ok(())
}
}
let data = &by_src[&src_id];
let repr_path = src_mgr.get_file_repr_path(src_id);
let path_ex = format!(
"{repr_path}:{}:{}",
data.first_span_line + 1,
data.first_span_col + 1,
);
out.push((" ".repeat(max_line_no_width), TextPartKind::Space));
out.push(("-->".into(), TextPartKind::Margin));
out.push((" ".into(), TextPartKind::Space));
out.push((path_ex, TextPartKind::Path));
out.push(("\n".into(), TextPartKind::Space));
out.push((" ".repeat(max_line_no_width + 1), TextPartKind::Space));
out.push((margin_line.into(), TextPartKind::Margin));
out.push(("\n".into(), TextPartKind::Space));
data.annots.render(max_line_no_width, 0, 0, Output(out));
}
}
fn get_main_style() -> sourceannot::MainStyle<TextPartKind> {
sourceannot::MainStyle {
margin: Some(sourceannot::MarginStyle {
line_char: '|',
discontinuity_chars: [' ', ' ', ':'],
meta: TextPartKind::Margin,
}),
horizontal_char: '_',
vertical_char: '|',
top_vertical_char: '/',
top_corner_char: ' ',
bottom_corner_char: '|',
spaces_meta: TextPartKind::Space,
text_normal_meta: TextPartKind::TextNormal,
text_alt_meta: TextPartKind::TextAlt,
}
}
fn get_error_annot_style() -> sourceannot::AnnotStyle<TextPartKind> {
sourceannot::AnnotStyle {
caret: '^',
text_normal_meta: TextPartKind::ErrorTextNormal,
text_alt_meta: TextPartKind::ErrorTextAlt,
line_meta: TextPartKind::ErrorLabel,
}
}
fn get_note_annot_style() -> sourceannot::AnnotStyle<TextPartKind> {
sourceannot::AnnotStyle {
caret: '-',
text_normal_meta: TextPartKind::TextNormal,
text_alt_meta: TextPartKind::TextAlt,
line_meta: TextPartKind::NoteLabel,
}
}