vesti 0.13.1

A preprocessor that compiles into LaTeX
use std::io::{self, BufWriter, Write};
use std::path::Path;

use crossterm::{
    queue,
    style::{Attribute, Color, ContentStyle, Print, SetAttribute, SetForegroundColor, SetStyle},
};

use super::{Error, VestiErr};
use crate::location::Span;

const BOLD_TEXT: SetAttribute = SetAttribute(Attribute::Bold);
const ERR_COLOR: SetForegroundColor = SetForegroundColor(Color::Red);
const NOTE_COLOR: SetForegroundColor = SetForegroundColor(Color::DarkCyan);
const BLUE_COLOR: SetForegroundColor = SetForegroundColor(Color::DarkBlue);
const RESET_STYLE: SetAttribute = SetAttribute(Attribute::Reset);

pub fn pretty_print<const PRINT_NOTE: bool>(
    source: Option<&str>,
    vesti_error: VestiErr,
    filepath: Option<&Path>,
) -> io::Result<()> {
    let mut stdout = BufWriter::new(io::stdout());
    let err_title_color: SetStyle = SetStyle(ContentStyle {
        foreground_color: Some(Color::White),
        attributes: Attribute::Bold.into(),
        ..Default::default()
    });

    let lines = source.map(|inner| inner.lines());
    let err_code = vesti_error.err_code();
    let err_str = vesti_error.err_str();

    // print actural error message
    queue!(
        stdout,
        BOLD_TEXT,
        ERR_COLOR,
        Print(format!("error[E{0:04X}]", err_code)),
        err_title_color,
        Print(format!(": {}\n", err_str)),
        RESET_STYLE,
    )?;

    if let VestiErr::ParseErr {
        location: Span { start, end },
        ..
    } = &vesti_error
    {
        let start_row_num = format!("{}", start.row());

        // If the filepath of the given input one is found, print it with error location
        if let Some(m_filepath) = filepath {
            queue!(
                stdout,
                Print(" ".repeat(start_row_num.len())),
                BOLD_TEXT,
                BLUE_COLOR,
                Print("--> "),
                RESET_STYLE,
                Print(m_filepath.to_str().unwrap()),
                Print(format!(":{}:{}\n", start.row(), start.column())),
            )?;
        }

        queue!(
            stdout,
            BOLD_TEXT,
            BLUE_COLOR,
            Print(" ".repeat(start_row_num.len().saturating_add(1))),
            Print("|\n"),
            Print(&start_row_num),
            Print(" | "),
            RESET_STYLE,
        )?;
        if let Some(mut inner) = lines {
            queue!(stdout, Print(inner.nth(start.row() - 1).unwrap()))?;
        }
        queue!(stdout, Print("\n"))?;

        // Print an error message with multiple lines
        let padding_space = end.column().saturating_sub(start.column()) + 1;
        queue!(
            stdout,
            BOLD_TEXT,
            BLUE_COLOR,
            Print(" ".repeat(start_row_num.len().saturating_add(1))),
            Print("| "),
            Print(" ".repeat(start.column().saturating_sub(1))),
            ERR_COLOR,
            Print("^".repeat(end.column().saturating_sub(start.column()))),
            Print(" "),
        )?;

        for (i, msg) in vesti_error.err_detail_str().iter().enumerate() {
            if i == 0 {
                queue!(stdout, Print(msg), Print("\n"))?;
            } else {
                queue!(
                    stdout,
                    BOLD_TEXT,
                    BLUE_COLOR,
                    Print(" ".repeat(start_row_num.len().saturating_add(1))),
                    Print("| "),
                    Print(" ".repeat(start.column().saturating_sub(1))),
                    ERR_COLOR,
                    Print(" ".repeat(padding_space)),
                    Print(msg),
                    Print("\n"),
                )?;
            }
        }
    }
    queue!(stdout, RESET_STYLE)?;

    // print note message if it is needed.
    // The message for this is generated by calling the crate::error::Error::err_detail_str.
    // TODO: make new call method to support this
    if PRINT_NOTE {
        queue!(
            stdout,
            BOLD_TEXT,
            NOTE_COLOR,
            Print("note: ".to_string()),
            RESET_STYLE,
        )?;
        for (idx, line) in vesti_error.err_detail_str().into_iter().enumerate() {
            if idx == 0 {
                queue!(stdout, Print(format!("{line}\n")))?;
            } else {
                queue!(stdout, Print(format!("      {line}\n")))?;
            }
        }
    }

    stdout.flush()?;

    Ok(())
}

// separate function for non-colorizing message
pub fn plain_print<const PRINT_NOTE: bool>(
    source: Option<&str>,
    vesti_error: VestiErr,
    filepath: Option<&Path>,
) -> io::Result<()> {
    let mut stdout = BufWriter::new(io::stdout());

    let lines = source.map(|inner| inner.lines());
    let err_code = vesti_error.err_code();
    let err_str = vesti_error.err_str();

    // print actural error message
    queue!(
        stdout,
        Print(format!("error[E{0:04X}]", err_code)),
        Print(format!(": {}\n", err_str)),
    )?;

    if let VestiErr::ParseErr {
        location: Span { start, end },
        ..
    } = &vesti_error
    {
        let start_row_num = format!("{}", start.row());

        // If the filepath of the given input one is found, print it with error location
        if let Some(m_filepath) = filepath {
            queue!(
                stdout,
                Print(" ".repeat(start_row_num.len())),
                Print("--> "),
                Print(m_filepath.to_str().unwrap()),
                Print(format!(":{}:{}\n", start.row(), start.column())),
            )?;
        }

        queue!(
            stdout,
            Print(" ".repeat(start_row_num.len().saturating_add(1))),
            Print("|\n"),
            Print(&start_row_num),
            Print(" | "),
        )?;
        if let Some(mut inner) = lines {
            queue!(stdout, Print(inner.nth(start.row() - 1).unwrap()))?;
        }
        queue!(stdout, Print("\n"))?;

        // Print an error message with multiple lines
        let padding_space = end.column().saturating_sub(start.column()) + 1;
        queue!(
            stdout,
            Print(" ".repeat(start_row_num.len().saturating_add(1))),
            Print("| "),
            Print(" ".repeat(start.column().saturating_sub(1))),
            Print("^".repeat(end.column().saturating_sub(start.column()))),
            Print(" "),
        )?;

        for (i, msg) in vesti_error.err_detail_str().iter().enumerate() {
            if i == 0 {
                queue!(stdout, Print(msg), Print("\n"))?;
            } else {
                queue!(
                    stdout,
                    Print(" ".repeat(start_row_num.len().saturating_add(1))),
                    Print("| "),
                    Print(" ".repeat(start.column().saturating_sub(1))),
                    Print(" ".repeat(padding_space)),
                    Print(msg),
                    Print("\n"),
                )?;
            }
        }
    }
    // print note message if it is needed.
    // The message for this is generated by calling the crate::error::Error::err_detail_str.
    // TODO: make new call method to support this
    if PRINT_NOTE {
        queue!(stdout, Print("note: ".to_string()),)?;
        for (idx, line) in vesti_error.err_detail_str().into_iter().enumerate() {
            if idx == 0 {
                queue!(stdout, Print(format!("{line}\n")))?;
            } else {
                queue!(stdout, Print(format!("      {line}\n")))?;
            }
        }
    }

    stdout.flush()?;

    Ok(())
}