use ansiterm::{ANSIString, ANSIStrings};
use unicode_width::UnicodeWidthChar;
use crate::fileset::FileKind;
use crate::game::Game;
use crate::report::errors::Errors;
use crate::report::output_style::Styled;
use crate::report::{LogReport, PointedMessage, Severity};
const SPACES_PER_TAB: usize = 4;
const MAX_IDLE_SPACE: usize = 16;
pub fn log_report(errors: &mut Errors, report: &LogReport) {
log_line_title(errors, report);
log_pointer(errors, None, report.primary(), report.indentation(), report.severity);
report.pointers.windows(2).for_each(|pointers| {
log_pointer(
errors,
Some(&pointers[0]),
&pointers[1],
report.indentation(),
report.severity,
);
});
if let Some(info) = &report.info {
log_line_info(errors, report.indentation(), info);
}
_ = writeln!(errors.output.borrow_mut());
}
fn log_pointer(
errors: &mut Errors,
previous: Option<&PointedMessage>,
pointer: &PointedMessage,
indentation: usize,
severity: Severity,
) {
if previous.is_none() || !previous.unwrap().loc.same_file(pointer.loc) {
log_line_file_location(errors, pointer, indentation);
}
if pointer.loc.line == 0 {
return;
}
if let Some(line) = errors.cache.get_line(pointer.loc) {
let (line, removed, spaces) = line_spacing(line.to_owned());
log_line_from_source(errors, pointer, indentation, &line, spaces);
log_line_carets(errors, pointer, indentation, &line, removed, spaces, severity);
}
}
fn log_line_title(errors: &Errors, report: &LogReport) {
let line: &[ANSIString<'static>] = &[
errors
.styles
.style(Styled::Tag(report.severity, true))
.paint(format!("{}", report.severity)),
errors.styles.style(Styled::Tag(report.severity, false)).paint(format!("({})", report.key)),
errors.styles.style(Styled::Default).paint(": "),
errors.styles.style(Styled::ErrorMessage).paint(report.msg.clone()),
];
_ = writeln!(errors.output.borrow_mut(), "{}", ANSIStrings(line));
}
fn log_line_info(errors: &Errors, indentation: usize, info: &str) {
let line_info: &[ANSIString<'static>] = &[
errors.styles.style(Styled::Default).paint(format!("{:width$}", "", width = indentation)),
errors.styles.style(Styled::Default).paint(" "),
errors.styles.style(Styled::Location).paint("="),
errors.styles.style(Styled::Default).paint(" "),
errors.styles.style(Styled::InfoTag).paint("Info:"),
errors.styles.style(Styled::Default).paint(" "),
errors.styles.style(Styled::Info).paint(info.to_string()),
];
_ = writeln!(errors.output.borrow_mut(), "{}", ANSIStrings(line_info));
}
fn log_line_file_location(errors: &Errors, pointer: &PointedMessage, indentation: usize) {
let line_filename: &[ANSIString<'static>] = &[
errors.styles.style(Styled::Default).paint(format!("{:width$}", "", width = indentation)),
errors.styles.style(Styled::Location).paint("-->"),
errors.styles.style(Styled::Default).paint(" "),
errors
.styles
.style(Styled::Location)
.paint(format!("[{}]", kind_tag(errors, pointer.loc.kind))),
errors.styles.style(Styled::Default).paint(" "),
errors
.styles
.style(Styled::Location)
.paint(format!("{}", pointer.loc.pathname().display())),
];
_ = writeln!(errors.output.borrow_mut(), "{}", ANSIStrings(line_filename));
}
fn log_line_from_source(
errors: &Errors,
pointer: &PointedMessage,
indentation: usize,
line: &str,
spaces: usize,
) {
let line_from_source: &[ANSIString<'static>] = &[
errors.styles.style(Styled::Location).paint(format!("{:indentation$}", pointer.loc.line,)),
errors.styles.style(Styled::Default).paint(" "),
errors.styles.style(Styled::Location).paint("|"),
errors.styles.style(Styled::Default).paint(" "),
errors.styles.style(Styled::SourceText).paint(format!("{:spaces$}{line}", "")),
];
_ = writeln!(errors.output.borrow_mut(), "{}", ANSIStrings(line_from_source));
}
fn log_line_carets(
errors: &Errors,
pointer: &PointedMessage,
indentation: usize,
line: &str,
removed: usize,
spaces: usize,
severity: Severity,
) {
let mut spacing = String::new();
for c in line.chars().take((pointer.loc.column as usize).saturating_sub(removed + 1)) {
if c == '\t' {
spacing.push('\t');
} else {
for _ in 0..c.width().unwrap_or(0) {
spacing.push(' ');
}
}
}
let line_carets: &[ANSIString] = &[
errors.styles.style(Styled::Default).paint(format!("{:indentation$}", "")),
errors.styles.style(Styled::Default).paint(" "),
errors.styles.style(Styled::Location).paint("|"),
errors.styles.style(Styled::Default).paint(format!(
"{:width$}{spacing}",
"",
width = spaces + 1
)),
errors.styles.style(Styled::Tag(severity, true)).paint(format!(
"{:^^width$}",
"",
width = pointer.length.max(1)
)),
errors.styles.style(Styled::Default).paint(" "),
errors
.styles
.style(Styled::Tag(severity, true))
.paint(pointer.msg.as_deref().map_or("", |_| "<-- ")),
errors
.styles
.style(Styled::Tag(severity, true))
.paint(pointer.msg.as_deref().unwrap_or("")),
];
_ = writeln!(errors.output.borrow_mut(), "{}", ANSIStrings(line_carets));
}
pub(crate) fn kind_tag(errors: &Errors, kind: FileKind) -> &str {
match kind {
FileKind::Internal => "Internal",
FileKind::Clausewitz => "Clausewitz",
FileKind::Jomini => "Jomini",
FileKind::Vanilla => match Game::game() {
#[cfg(feature = "ck3")]
Game::Ck3 => "CK3",
#[cfg(feature = "vic3")]
Game::Vic3 => "Vic3",
#[cfg(feature = "imperator")]
Game::Imperator => "Imperator",
},
FileKind::Dlc(idx) => &errors.loaded_dlcs_labels[idx as usize],
FileKind::LoadedMod(idx) => &errors.loaded_mods_labels[idx as usize],
FileKind::Mod => "MOD",
}
}
fn line_spacing(mut line: String) -> (String, usize, usize) {
let mut remove = 0;
let mut spaces = 0;
for c in line.chars() {
if c == ' ' {
spaces += 1;
} else if c == '\t' {
spaces += SPACES_PER_TAB;
} else {
break;
}
remove += 1;
}
spaces = spaces.min(MAX_IDLE_SPACE);
line.replace_range(..remove, "");
(line, remove, spaces)
}