#![allow(clippy::doc_markdown)]
use std::borrow::Cow;
use ratatui::style::{Color, Style};
#[derive(Clone, Debug)]
pub struct ColorScheme {
pub background: Color,
pub foreground: Color,
pub primary: Color,
pub accent: Color,
pub muted: Color,
pub border: Color,
pub user: Color,
pub user_bg: Color,
pub response: Color,
pub response_bg: Color,
pub thinking: Color,
pub thinking_bg: Color,
pub tool: Color,
pub tool_bg: Color,
pub success: Color,
pub warning: Color,
pub error: Color,
pub info: Color,
pub diff_add: Color,
pub diff_remove: Color,
pub diff_hunk: Color,
pub surface_bg: Color,
pub panel_bg: Color,
pub code_bg: Color,
pub selection_bg: Color,
pub diff_add_bg: Color,
pub diff_remove_bg: Color,
pub diff_hunk_bg: Color,
}
impl Default for ColorScheme {
fn default() -> Self {
Self::dark()
}
}
#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct ThemeStyles {
pub normal: Style,
pub primary: Style,
pub accent: Style,
pub muted: Style,
pub border: Style,
pub user: Style,
pub user_bg: Style,
pub response: Style,
pub response_bg: Style,
pub thinking: Style,
pub thinking_bg: Style,
pub tool: Style,
pub tool_bg: Style,
pub success: Style,
pub warning: Style,
pub error: Style,
pub info: Style,
pub diff_add: Style,
pub diff_remove: Style,
pub diff_hunk: Style,
pub surface_bg: Style,
pub panel_bg: Style,
pub code_bg: Style,
pub selection_bg: Style,
pub diff_add_bg: Style,
pub diff_remove_bg: Style,
pub diff_hunk_bg: Style,
}
impl ThemeStyles {
#[must_use]
pub fn from_colors(colors: &ColorScheme) -> Self {
Self {
normal: Style::default().fg(colors.foreground),
primary: Style::default().fg(colors.primary),
accent: Style::default().fg(colors.accent),
muted: Style::default().fg(colors.muted),
border: Style::default().fg(colors.border),
user: Style::default().fg(colors.user),
user_bg: Style::default().bg(colors.user_bg),
response: Style::default().fg(colors.response),
response_bg: Style::default().bg(colors.response_bg),
thinking: Style::default().fg(colors.thinking),
thinking_bg: Style::default().bg(colors.thinking_bg),
tool: Style::default().fg(colors.tool),
tool_bg: Style::default().bg(colors.tool_bg),
success: Style::default().fg(colors.success),
warning: Style::default().fg(colors.warning),
error: Style::default().fg(colors.error),
info: Style::default().fg(colors.info),
diff_add: Style::default().fg(colors.diff_add),
diff_remove: Style::default().fg(colors.diff_remove),
diff_hunk: Style::default().fg(colors.diff_hunk),
surface_bg: Style::default().bg(colors.surface_bg),
panel_bg: Style::default().bg(colors.panel_bg),
code_bg: Style::default().bg(colors.code_bg),
selection_bg: Style::default().bg(colors.selection_bg),
diff_add_bg: Style::default().bg(colors.diff_add_bg),
diff_remove_bg: Style::default().bg(colors.diff_remove_bg),
diff_hunk_bg: Style::default().bg(colors.diff_hunk_bg),
}
}
}
#[derive(Clone, Debug)]
pub struct Theme {
pub colors: ColorScheme,
pub styles: ThemeStyles,
pub name: Cow<'static, str>,
}
impl Default for Theme {
fn default() -> Self {
Self::dark()
}
}
impl Theme {
#[must_use]
pub fn dark() -> Self {
Self::from_scheme(ColorScheme::dark(), "dark")
}
#[must_use]
pub fn light() -> Self {
Self::from_scheme(ColorScheme::light(), "light")
}
#[must_use]
pub fn nord() -> Self {
Self::from_scheme(ColorScheme::nord(), "nord")
}
#[must_use]
pub fn catppuccin() -> Self {
Self::from_scheme(ColorScheme::catppuccin(), "catppuccin")
}
#[must_use]
pub fn github_dark() -> Self {
Self::from_scheme(ColorScheme::github_dark(), "github_dark")
}
#[must_use]
pub fn monokai() -> Self {
Self::from_scheme(ColorScheme::monokai(), "monokai")
}
#[must_use]
pub fn from_scheme(colors: ColorScheme, name: &'static str) -> Self {
let styles = ThemeStyles::from_colors(&colors);
Self {
colors,
styles,
name: Cow::Borrowed(name),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_constructors_produce_valid_themes() {
let _ = Theme::dark();
let _ = Theme::light();
let _ = Theme::nord();
let _ = Theme::catppuccin();
let _ = Theme::github_dark();
let _ = Theme::monokai();
}
#[test]
fn dark_theme_brightness_hierarchy() {
let t = Theme::dark();
let bgs = [
t.colors.background,
t.colors.response_bg,
t.colors.thinking_bg,
t.colors.surface_bg,
t.colors.user_bg,
t.colors.panel_bg,
];
let unique: std::collections::HashSet<_> = bgs.iter().collect();
assert!(
unique.len() >= 4,
"background slots should be mostly distinct, got {bgs:?}"
);
}
}