use super::{Theme, ThemeColor, ThemeConfig, ThemeMode, ThemeSet};
use crate::theme::highlight::HighlightTheme;
use rgpui::{App, Global, SharedString};
use std::{
collections::HashMap,
path::PathBuf,
rc::Rc,
sync::{Arc, LazyLock},
};
const DEFAULT_THEME: &str = include_str!("./default-theme.json");
#[cfg(feature = "bundled-themes")]
const EXTRA_THEMES: &str = include_str!("./themes/extra-themes.json");
pub static DEFAULT_THEME_COLORS: LazyLock<
HashMap<ThemeMode, (Arc<ThemeColor>, Arc<HighlightTheme>)>,
> = LazyLock::new(|| {
let mut colors = HashMap::new();
let themes: Vec<ThemeConfig> = serde_json::from_str::<ThemeSet>(DEFAULT_THEME)
.expect("Failed to parse themes/default.json")
.themes;
for theme in themes {
let mut theme_color = ThemeColor::default();
theme_color.apply_config(&theme, &ThemeColor::default());
let highlight_theme = HighlightTheme {
name: theme.name.to_string(),
appearance: theme.mode,
style: theme.highlight.unwrap_or_default(),
};
colors.insert(
theme.mode,
(Arc::new(theme_color), Arc::new(highlight_theme)),
);
}
colors
});
pub(super) fn init(cx: &mut App) {
cx.set_global(ThemeRegistry::default());
ThemeRegistry::global_mut(cx).init_default_themes();
cx.observe_global::<ThemeRegistry>(|cx| {
let mode = Theme::global(cx).mode;
let light_theme = Theme::global(cx).light_theme.name.clone();
let dark_theme = Theme::global(cx).dark_theme.name.clone();
if let Some(theme) = ThemeRegistry::global(cx)
.themes()
.get(&light_theme)
.cloned()
{
Theme::global_mut(cx).light_theme = theme;
}
if let Some(theme) = ThemeRegistry::global(cx).themes().get(&dark_theme).cloned() {
Theme::global_mut(cx).dark_theme = theme;
}
let theme_name = if mode.is_dark() {
dark_theme
} else {
light_theme
};
tracing::info!("Reload active theme: {:?}...", theme_name);
Theme::change(mode, None, cx);
cx.refresh_windows();
})
.detach();
}
#[derive(Default, Debug)]
pub struct ThemeRegistry {
themes_dir: PathBuf,
default_themes: HashMap<ThemeMode, Rc<ThemeConfig>>,
themes: HashMap<SharedString, Rc<ThemeConfig>>,
has_custom_themes: bool,
}
impl Global for ThemeRegistry {}
impl ThemeRegistry {
pub fn global(cx: &App) -> &Self {
cx.global::<Self>()
}
pub fn global_mut(cx: &mut App) -> &mut Self {
cx.global_mut::<Self>()
}
pub fn themes(&self) -> &HashMap<SharedString, Rc<ThemeConfig>> {
&self.themes
}
pub fn sorted_themes(&self) -> Vec<&Rc<ThemeConfig>> {
let mut themes = self.themes.values().collect::<Vec<_>>();
themes.sort_by(|a, b| {
b.is_default
.cmp(&a.is_default)
.then(a.mode.cmp(&b.mode))
.then(a.name.to_lowercase().cmp(&b.name.to_lowercase()))
});
themes
}
pub fn default_themes(&self) -> &HashMap<ThemeMode, Rc<ThemeConfig>> {
&self.default_themes
}
pub fn default_light_theme(&self) -> &Rc<ThemeConfig> {
&self.default_themes[&ThemeMode::Light]
}
pub fn default_dark_theme(&self) -> &Rc<ThemeConfig> {
&self.default_themes[&ThemeMode::Dark]
}
pub fn load_themes_from_str(&mut self, content: &str) -> anyhow::Result<()> {
let theme_set = serde_json::from_str::<ThemeSet>(content)?;
for theme in theme_set.themes {
if !self.themes.contains_key(&theme.name) {
let theme_name = theme.name.clone();
self.themes.insert(theme_name, Rc::new(theme));
self.has_custom_themes = true;
}
}
Ok(())
}
fn init_default_themes(&mut self) {
let default_themes: Vec<ThemeConfig> = serde_json::from_str::<ThemeSet>(DEFAULT_THEME)
.expect("failed to parse default theme.")
.themes;
for theme in default_themes.into_iter() {
if theme.mode.is_dark() {
self.default_themes.insert(ThemeMode::Dark, Rc::new(theme));
} else {
self.default_themes.insert(ThemeMode::Light, Rc::new(theme));
}
}
self.themes_dir = PathBuf::from("./themes");
self.themes = self
.default_themes
.values()
.map(|theme| {
let name = theme.name.clone();
(name, Rc::clone(theme))
})
.collect();
#[cfg(feature = "bundled-themes")]
{
if let Err(e) = self.load_themes_from_str(EXTRA_THEMES) {
tracing::error!("Failed to load bundled extra themes: {e}");
}
}
}
}
#[cfg(all(test, feature = "bundled-themes"))]
mod tests {
use super::*;
#[test]
fn test_bundled_themes_parse_and_load() {
let mut registry = ThemeRegistry::default();
registry
.load_themes_from_str(EXTRA_THEMES)
.expect("bundled extra themes should parse");
assert_eq!(
registry.themes().len(),
37,
"themes: {:?}",
registry.themes().keys()
);
}
}