use ratatui_core::style::Color;
use crate::style::{CodeTheme, Theme};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct NamedTheme {
pub name: &'static str,
pub label: &'static str,
pub theme: Theme,
}
pub const PRESETS: &[NamedTheme] = &[
NamedTheme {
name: "solarized-dark",
label: "Solarized Dark",
theme: SOLARIZED_DARK,
},
NamedTheme {
name: "solarized-light",
label: "Solarized Light",
theme: SOLARIZED_LIGHT,
},
NamedTheme {
name: "gruvbox-dark",
label: "Gruvbox Dark",
theme: GRUVBOX_DARK,
},
NamedTheme {
name: "light",
label: "Light",
theme: LIGHT,
},
NamedTheme {
name: "dracula",
label: "Dracula",
theme: DRACULA,
},
];
pub fn by_name(name: &str) -> Option<Theme> {
PRESETS
.iter()
.find(|p| p.name.eq_ignore_ascii_case(name))
.map(|p| p.theme)
}
pub const SOLARIZED_DARK: Theme = Theme {
background: Color::Rgb(0, 43, 54), surface: Color::Rgb(7, 54, 66), text: Color::Rgb(131, 148, 150), muted: Color::Rgb(88, 110, 117), dim: Color::Rgb(58, 82, 92), accent: Color::Rgb(38, 139, 210), accent_alt: Color::Rgb(42, 161, 152), border: Color::Rgb(58, 82, 92),
border_focused: Color::Rgb(38, 139, 210),
selection_bg: Color::Rgb(7, 54, 66), selection_fg: Color::Rgb(147, 161, 161), code: CodeTheme {
heading: Color::Rgb(147, 161, 161), link: Color::Rgb(38, 139, 210), background: Color::Rgb(7, 54, 66), text: Color::Rgb(131, 148, 150), label: Color::Rgb(88, 110, 117), keyword: Color::Rgb(133, 153, 0), function: Color::Rgb(38, 139, 210), type_name: Color::Rgb(181, 137, 0), constant: Color::Rgb(211, 54, 130), string: Color::Rgb(42, 161, 152), comment: Color::Rgb(88, 110, 117), punctuation: Color::Rgb(101, 123, 131), },
};
pub const SOLARIZED_LIGHT: Theme = Theme {
background: Color::Rgb(253, 246, 227), surface: Color::Rgb(238, 232, 213), text: Color::Rgb(101, 123, 131), muted: Color::Rgb(147, 161, 161), dim: Color::Rgb(203, 198, 182), accent: Color::Rgb(38, 139, 210), accent_alt: Color::Rgb(42, 161, 152), border: Color::Rgb(147, 161, 161), border_focused: Color::Rgb(38, 139, 210),
selection_bg: Color::Rgb(238, 232, 213), selection_fg: Color::Rgb(88, 110, 117), code: CodeTheme {
heading: Color::Rgb(88, 110, 117), link: Color::Rgb(38, 139, 210), background: Color::Rgb(238, 232, 213), text: Color::Rgb(101, 123, 131), label: Color::Rgb(147, 161, 161), keyword: Color::Rgb(133, 153, 0), function: Color::Rgb(38, 139, 210), type_name: Color::Rgb(181, 137, 0), constant: Color::Rgb(211, 54, 130), string: Color::Rgb(42, 161, 152), comment: Color::Rgb(147, 161, 161), punctuation: Color::Rgb(101, 123, 131), },
};
pub const GRUVBOX_DARK: Theme = Theme {
background: Color::Rgb(40, 40, 40), surface: Color::Rgb(60, 56, 54), text: Color::Rgb(235, 219, 178), muted: Color::Rgb(168, 153, 132), dim: Color::Rgb(102, 92, 84), accent: Color::Rgb(254, 128, 25), accent_alt: Color::Rgb(142, 192, 124), border: Color::Rgb(102, 92, 84), border_focused: Color::Rgb(254, 128, 25),
selection_bg: Color::Rgb(80, 73, 69), selection_fg: Color::Rgb(251, 241, 199), code: CodeTheme {
heading: Color::Rgb(235, 219, 178), link: Color::Rgb(131, 165, 152), background: Color::Rgb(29, 32, 33), text: Color::Rgb(235, 219, 178), label: Color::Rgb(168, 153, 132), keyword: Color::Rgb(251, 73, 52), function: Color::Rgb(142, 192, 124), type_name: Color::Rgb(250, 189, 47), constant: Color::Rgb(211, 134, 155), string: Color::Rgb(184, 187, 38), comment: Color::Rgb(146, 131, 116), punctuation: Color::Rgb(168, 153, 132), },
};
pub const LIGHT: Theme = Theme {
background: Color::Rgb(250, 250, 250),
surface: Color::Rgb(240, 240, 240),
text: Color::Rgb(30, 30, 30),
muted: Color::Rgb(110, 110, 110),
dim: Color::Rgb(190, 190, 190),
accent: Color::Rgb(30, 102, 204), accent_alt: Color::Rgb(0, 135, 120), border: Color::Rgb(200, 200, 200),
border_focused: Color::Rgb(30, 102, 204),
selection_bg: Color::Rgb(210, 226, 250),
selection_fg: Color::Rgb(20, 40, 70),
code: CodeTheme {
heading: Color::Rgb(20, 20, 20),
link: Color::Rgb(30, 102, 204),
background: Color::Rgb(240, 240, 240),
text: Color::Rgb(40, 40, 40),
label: Color::Rgb(110, 110, 110),
keyword: Color::Rgb(170, 13, 145), function: Color::Rgb(30, 102, 204), type_name: Color::Rgb(0, 92, 197),
constant: Color::Rgb(155, 76, 0), string: Color::Rgb(24, 128, 56), comment: Color::Rgb(120, 130, 120),
punctuation: Color::Rgb(90, 90, 90),
},
};
pub const DRACULA: Theme = Theme {
background: Color::Rgb(40, 42, 54),
surface: Color::Rgb(68, 71, 90), text: Color::Rgb(248, 248, 242),
muted: Color::Rgb(98, 114, 164), dim: Color::Rgb(73, 76, 98),
accent: Color::Rgb(189, 147, 249), accent_alt: Color::Rgb(255, 121, 198), border: Color::Rgb(98, 114, 164),
border_focused: Color::Rgb(189, 147, 249),
selection_bg: Color::Rgb(68, 71, 90),
selection_fg: Color::Rgb(248, 248, 242),
code: CodeTheme {
heading: Color::Rgb(248, 248, 242),
link: Color::Rgb(139, 233, 253), background: Color::Rgb(33, 34, 44),
text: Color::Rgb(248, 248, 242),
label: Color::Rgb(98, 114, 164),
keyword: Color::Rgb(255, 121, 198), function: Color::Rgb(80, 250, 123), type_name: Color::Rgb(139, 233, 253), constant: Color::Rgb(189, 147, 249), string: Color::Rgb(241, 250, 140), comment: Color::Rgb(98, 114, 164),
punctuation: Color::Rgb(248, 248, 242),
},
};
impl Theme {
pub const fn solarized_dark() -> Theme {
SOLARIZED_DARK
}
pub const fn solarized_light() -> Theme {
SOLARIZED_LIGHT
}
pub const fn gruvbox_dark() -> Theme {
GRUVBOX_DARK
}
pub const fn light() -> Theme {
LIGHT
}
pub const fn dracula() -> Theme {
DRACULA
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn by_name_matches_presets_case_insensitively() {
for p in PRESETS {
assert_eq!(by_name(p.name), Some(p.theme));
assert_eq!(by_name(&p.name.to_uppercase()), Some(p.theme));
}
assert_eq!(by_name("no-such-theme"), None);
}
#[test]
fn constructors_match_catalog() {
assert_eq!(Theme::solarized_dark(), by_name("solarized-dark").unwrap());
assert_eq!(
Theme::solarized_light(),
by_name("solarized-light").unwrap()
);
assert_eq!(Theme::gruvbox_dark(), by_name("gruvbox-dark").unwrap());
assert_eq!(Theme::light(), by_name("light").unwrap());
assert_eq!(Theme::dracula(), by_name("dracula").unwrap());
}
#[test]
fn preset_names_are_unique_and_kebab_case() {
let mut seen = std::collections::HashSet::new();
for p in PRESETS {
assert!(seen.insert(p.name), "duplicate preset name {}", p.name);
assert!(
p.name
.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-'),
"preset name {} is not kebab-case",
p.name
);
assert!(!p.label.is_empty());
}
}
}