mdfried 0.22.1

A markdown viewer for the terminal that renders images and big headers
use ansi_to_tui::IntoText;
use arborium::AnsiHighlighter;
use itertools::Itertools as _;
use mdfrier::ratatui::Theme as _;
use ratatui::{
    style::{Color, Stylize as _},
    text::{Line, Text},
};

use crate::error::Error;

pub struct Highlighter {
    hl: AnsiHighlighter,
    bg: Color,
}

impl Highlighter {
    pub fn new(mdfried_theme: &crate::config::Theme) -> Self {
        let theme = arborium::theme::builtin::tokyo_night().clone();
        let bg = mdfried_theme.code_bg();
        let hl = AnsiHighlighter::new(theme);
        Self { hl, bg }
    }

    pub fn highlight(
        &mut self,
        language: &str,
        lines: Vec<Line<'_>>,
    ) -> Result<Text<'static>, Error> {
        let code = lines.into_iter().map(|line| line.to_string()).join("\n");
        self.hl
            .highlight(language, &code)
            .map_err(Into::<Error>::into)
            .and_then(|colored| IntoText::into_text(&colored).map_err(Into::<Error>::into))
            .map(|mut text| {
                text.lines = text
                    .lines
                    .into_iter()
                    .map(|mut line| {
                        line.spans = line
                            .spans
                            .into_iter()
                            .map(|span| span.bg(self.bg))
                            .collect();
                        line.bg(self.bg)
                    })
                    .collect();
                text
            })
    }

    pub fn fork(&self) -> Self {
        Self {
            hl: self.hl.fork(),
            bg: self.bg,
        }
    }
}

#[cfg(test)]
#[expect(clippy::unwrap_used)]
mod tests {
    use super::*;

    #[test]
    fn highlight_languages() {
        let theme = crate::config::Theme::default();
        let mut hl = Highlighter::new(&theme);

        let result = hl
            .highlight(
                "ada",
                vec![
                    Line::from("with Ada.Text_IO;"),
                    Line::from("procedure Hello is"),
                    Line::from("begin"),
                    Line::from("   Ada.Text_IO.Put_Line (\"Hello, World!\");"),
                    Line::from("end Hello;"),
                ],
            )
            .unwrap();

        assert!(
            result
                .lines
                .into_iter()
                .flat_map(|line| line.spans)
                .any(|span| span.style.fg.is_some())
        );
    }
}