monothink 0.5.1

A totally non-agentic coding assistant
/*
 * This file is part of monothink.
 *
 * monothink is free software: you can redistribute it and/or modify it under the terms
 * of the GNU General Public License as published by the Free Software Foundation,
 * either version 3 of the License, or (at your option) any later version.
 *
 * monothink is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with Wavething.
 * If not, see <https://www.gnu.org/licenses/>.
 */

use directories::ProjectDirs;
use lazy_static::lazy_static;
use ratatui::style::{Color, Style};
use syntect::{
    highlighting::{Theme, ThemeSet},
    parsing::SyntaxSet,
};
use unicode_width::UnicodeWidthStr;

pub type SyntectStyle = syntect::highlighting::Style;

lazy_static! {
    pub static ref GUTTER_WIDTH: u16 = UnicodeWidthStr::width("▌ ") as u16;
}

pub const LIGHT_BLUE: Color = Color::Rgb(0, 122, 204);
pub const PURPLE: Color = Color::Rgb(197, 134, 192);
// pub const WHITE: Color = Color::Rgb(255, 255, 255);
// pub const BLACK: Color = Color::Rgb(0, 0, 0);
pub const LIGHT_GRAY: Color = Color::Rgb(205, 205, 205);
// pub const DARK_GRAY: Color = Color::Rgb(133, 133, 133);
pub const DARK_GRAY2: Color = Color::Rgb(139, 148, 158);
pub const DARK_GRAY3: Color = Color::Rgb(40, 40, 40);
pub const NEAR_BLACK: Color = Color::Rgb(20, 20, 20);
pub const NEAR_BLACK2: Color = Color::Rgb(30, 30, 30);
// pub const ANTHRAZITE: Color = Color::Rgb(54, 69, 79);

// check colors
pub const METALLIC_BLUE: Color = Color::Rgb(101, 154, 211);
pub const LIGHT_TOURQUOISE: Color = Color::Rgb(168, 218, 216);
pub const TOURQUOISE: Color = Color::Rgb(82, 197, 176);
pub const DARK_PURPLE: Color = Color::Rgb(97, 40, 119);
pub const DARK_BLUE: Color = Color::Rgb(20, 56, 92);
// pub const DARK_METALLIC_BLUE: Color = Color::Rgb(41, 97, 153);

#[derive(Clone, Debug)]
pub struct TextStyle {
    pub fg_color: Color,
    pub bg_color: Color,
    pub bold: bool,
    pub italic: bool,
    pub underline: bool,
    pub strikethrough: bool,
}

impl TextStyle {
    pub fn inverted_default() -> Self {
        Self {
            fg_color: NEAR_BLACK2,
            bg_color: LIGHT_GRAY,
            bold: false,
            italic: false,
            underline: false,
            strikethrough: false,
        }
    }

    pub fn with_fg_color(mut self, color: Color) -> Self {
        self.fg_color = color;

        self
    }

    pub fn with_bg_color(mut self, color: Color) -> Self {
        self.bg_color = color;

        self
    }

    pub fn with_bold(mut self) -> Self {
        self.bold = true;

        self
    }

    pub fn with_italic(mut self) -> Self {
        self.italic = true;

        self
    }

    pub fn with_underline(mut self) -> Self {
        self.underline = true;

        self
    }

    pub fn with_strikethrough(mut self) -> Self {
        self.strikethrough = true;

        self
    }
}

impl Default for TextStyle {
    fn default() -> Self {
        Self {
            fg_color: LIGHT_GRAY,
            bg_color: NEAR_BLACK2,
            bold: false,
            italic: false,
            underline: false,
            strikethrough: false,
        }
    }
}

impl From<TextStyle> for Style {
    fn from(val: TextStyle) -> Self {
        // is there a cleaner way to do it?
        let mut style = Style::new().fg(val.fg_color).bg(val.bg_color);
        style = if val.bold { style.bold() } else { style };
        style = if val.italic { style.italic() } else { style };
        style = if val.underline {
            style.underlined()
        } else {
            style
        };
        style = if val.strikethrough {
            style.crossed_out()
        } else {
            style
        };

        style
    }
}

#[derive(Clone, Debug, Default)]
pub struct HighlightConfig {
    pub syntax_set: SyntaxSet,
    pub theme: Theme,
}

impl HighlightConfig {
    pub fn new() -> Self {
        let syn_set = SyntaxSet::load_defaults_newlines();
        let mut theme_set = ThemeSet::load_defaults();

        if let Some(proj_dirs) = ProjectDirs::from("org", "codemonk", "monothink") {
            let mut share_dir = proj_dirs.data_dir().to_path_buf();
            share_dir.push("themes");
            let share_path = share_dir.as_path();
            theme_set
                .add_from_folder(share_path)
                .expect("Failed to load theme folder.");
        }

        let theme = theme_set.themes.get("darkplus").unwrap_or_else(|| {
            theme_set
                .themes
                .get("base16-ocean.dark")
                .expect("Cannot load default theme")
        });

        Self {
            syntax_set: syn_set,
            theme: theme.clone(),
        }
    }
}