From 5f1dcb76feb51e0e786662f856607d95b40afcce Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Sat, 27 Jan 2024 16:03:04 -0500 Subject: [PATCH] Load JSON themes (#6893) This PR changes the theme loading to use the JSON themes bundled with the binary rather then the Rust theme definitions. ### Performance I profiled this using `cargo run --release` to see what the speed differences would be now that we're deserializing JSON: **Before:** `ThemeRegistry::load_user_themes` took 16.656666ms **After:** `ThemeRegistry::load_user_themes` took 18.784875ms It's slightly slower, but not by much. There is probably some work we could do here to bring down the theme loading time in general. Release Notes: - N/A --- {crates/theme/src => assets}/themes/LICENSES | 0 crates/storybook/src/storybook.rs | 2 +- crates/theme/src/registry.rs | 127 +++++++++++++------ crates/theme/src/schema.rs | 30 ++++- crates/theme/src/theme.rs | 18 ++- crates/zed/src/main.rs | 2 +- script/generate-licenses | 2 +- 7 files changed, 133 insertions(+), 48 deletions(-) rename {crates/theme/src => assets}/themes/LICENSES (100%) diff --git a/crates/theme/src/themes/LICENSES b/assets/themes/LICENSES similarity index 100% rename from crates/theme/src/themes/LICENSES rename to assets/themes/LICENSES diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs index 1c5ffb494b..70e830222f 100644 --- a/crates/storybook/src/storybook.rs +++ b/crates/storybook/src/storybook.rs @@ -69,7 +69,7 @@ fn main() { .unwrap(); cx.set_global(store); - theme::init(theme::LoadThemes::All, cx); + theme::init(theme::LoadThemes::All(Box::new(Assets)), cx); let selector = story_selector; diff --git a/crates/theme/src/registry.rs b/crates/theme/src/registry.rs index 015814e3b2..43f6d5b07e 100644 --- a/crates/theme/src/registry.rs +++ b/crates/theme/src/registry.rs @@ -2,12 +2,13 @@ use std::collections::HashMap; use std::sync::Arc; use anyhow::{anyhow, Result}; -use gpui::{HighlightStyle, SharedString}; +use gpui::{AssetSource, HighlightStyle, SharedString}; use refineable::Refineable; use crate::{ - Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors, Theme, ThemeColors, - ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily, + try_parse_color, Appearance, AppearanceContent, PlayerColor, PlayerColors, StatusColors, + SyntaxTheme, SystemColors, Theme, ThemeColors, ThemeContent, ThemeFamily, ThemeFamilyContent, + ThemeStyles, }; #[derive(Debug, Clone)] @@ -17,10 +18,27 @@ pub struct ThemeMeta { } pub struct ThemeRegistry { + assets: Box, themes: HashMap>, } impl ThemeRegistry { + pub fn new(assets: Box) -> Self { + let mut registry = Self { + assets, + themes: HashMap::new(), + }; + + // We're loading our new versions of the One themes by default, as + // we need them to be loaded for tests. + // + // These themes will get overwritten when `load_user_themes` is called + // when Zed starts, so the One variants used will be the ones ported from Zed1. + registry.insert_theme_families([crate::one_themes::one_family()]); + + registry + } + fn insert_theme_families(&mut self, families: impl IntoIterator) { for family in families.into_iter() { self.insert_themes(family.themes); @@ -34,48 +52,78 @@ impl ThemeRegistry { } #[allow(unused)] - fn insert_user_theme_families(&mut self, families: impl IntoIterator) { + fn insert_user_theme_families( + &mut self, + families: impl IntoIterator, + ) { for family in families.into_iter() { self.insert_user_themes(family.themes); } } #[allow(unused)] - fn insert_user_themes(&mut self, themes: impl IntoIterator) { + fn insert_user_themes(&mut self, themes: impl IntoIterator) { self.insert_themes(themes.into_iter().map(|user_theme| { let mut theme_colors = match user_theme.appearance { - Appearance::Light => ThemeColors::light(), - Appearance::Dark => ThemeColors::dark(), + AppearanceContent::Light => ThemeColors::light(), + AppearanceContent::Dark => ThemeColors::dark(), }; - theme_colors.refine(&user_theme.styles.colors); + theme_colors.refine(&user_theme.style.theme_colors_refinement()); let mut status_colors = match user_theme.appearance { - Appearance::Light => StatusColors::light(), - Appearance::Dark => StatusColors::dark(), + AppearanceContent::Light => StatusColors::light(), + AppearanceContent::Dark => StatusColors::dark(), }; - status_colors.refine(&user_theme.styles.status); + status_colors.refine(&user_theme.style.status_colors_refinement()); let mut player_colors = match user_theme.appearance { - Appearance::Light => PlayerColors::light(), - Appearance::Dark => PlayerColors::dark(), + AppearanceContent::Light => PlayerColors::light(), + AppearanceContent::Dark => PlayerColors::dark(), }; - if let Some(player_colors_from_theme) = user_theme.styles.player { - player_colors = player_colors_from_theme; + if !user_theme.style.players.is_empty() { + player_colors = PlayerColors( + user_theme + .style + .players + .into_iter() + .map(|player| PlayerColor { + cursor: player + .cursor + .as_ref() + .and_then(|color| try_parse_color(&color).ok()) + .unwrap_or_default(), + background: player + .background + .as_ref() + .and_then(|color| try_parse_color(&color).ok()) + .unwrap_or_default(), + selection: player + .selection + .as_ref() + .and_then(|color| try_parse_color(&color).ok()) + .unwrap_or_default(), + }) + .collect(), + ); } let mut syntax_colors = match user_theme.appearance { - Appearance::Light => SyntaxTheme::light(), - Appearance::Dark => SyntaxTheme::dark(), + AppearanceContent::Light => SyntaxTheme::light(), + AppearanceContent::Dark => SyntaxTheme::dark(), }; - if let Some(user_syntax) = user_theme.styles.syntax { - syntax_colors.highlights = user_syntax - .highlights + if !user_theme.style.syntax.is_empty() { + syntax_colors.highlights = user_theme + .style + .syntax .iter() .map(|(syntax_token, highlight)| { ( syntax_token.clone(), HighlightStyle { - color: highlight.color, + color: highlight + .color + .as_ref() + .and_then(|color| try_parse_color(&color).ok()), font_style: highlight.font_style.map(Into::into), font_weight: highlight.font_weight.map(Into::into), ..Default::default() @@ -88,7 +136,10 @@ impl ThemeRegistry { Theme { id: uuid::Uuid::new_v4().to_string(), name: user_theme.name.into(), - appearance: user_theme.appearance, + appearance: match user_theme.appearance { + AppearanceContent::Light => Appearance::Light, + AppearanceContent::Dark => Appearance::Dark, + }, styles: ThemeStyles { system: SystemColors::default(), colors: theme_colors, @@ -124,24 +175,28 @@ impl ThemeRegistry { } pub fn load_user_themes(&mut self) { - #[cfg(not(feature = "importing-themes"))] - self.insert_user_theme_families(crate::all_user_themes()); + let theme_paths = self + .assets + .list("themes/") + .unwrap() + .into_iter() + .filter(|path| path.ends_with(".json")); + + for path in theme_paths { + let theme = self + .assets + .load(&path) + .expect(&format!("Failed to load theme '{path}'")); + + let theme_family: ThemeFamilyContent = serde_json::from_slice(&theme).unwrap(); + + self.insert_user_theme_families([theme_family]); + } } } impl Default for ThemeRegistry { fn default() -> Self { - let mut registry = Self { - themes: HashMap::default(), - }; - - // We're loading our new versions of the One themes by default, as - // we need them to be loaded for tests. - // - // These themes will get overwritten when `load_user_themes` is called - // when Zed starts, so the One variants used will be the ones ported from Zed1. - registry.insert_theme_families([crate::one_themes::one_family()]); - - registry + Self::new(Box::new(())) } } diff --git a/crates/theme/src/schema.rs b/crates/theme/src/schema.rs index 904316d782..6687d0cdc0 100644 --- a/crates/theme/src/schema.rs +++ b/crates/theme/src/schema.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use gpui::{HighlightStyle, Hsla}; +use gpui::{FontStyle, FontWeight, HighlightStyle, Hsla}; use indexmap::IndexMap; use palette::FromColor; use schemars::gen::SchemaGenerator; @@ -11,7 +11,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; use crate::{StatusColorsRefinement, ThemeColorsRefinement}; -fn try_parse_color(color: &str) -> Result { +pub(crate) fn try_parse_color(color: &str) -> Result { let rgba = gpui::Rgba::try_from(color)?; let rgba = palette::rgb::Srgba::from_components((rgba.r, rgba.g, rgba.b, rgba.a)); let hsla = palette::Hsla::from_color(rgba); @@ -1171,6 +1171,16 @@ pub enum FontStyleContent { Oblique, } +impl From for FontStyle { + fn from(value: FontStyleContent) -> Self { + match value { + FontStyleContent::Normal => FontStyle::Normal, + FontStyleContent::Italic => FontStyle::Italic, + FontStyleContent::Oblique => FontStyle::Oblique, + } + } +} + #[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)] #[repr(u16)] pub enum FontWeightContent { @@ -1211,6 +1221,22 @@ impl JsonSchema for FontWeightContent { } } +impl From for FontWeight { + fn from(value: FontWeightContent) -> Self { + match value { + FontWeightContent::Thin => FontWeight::THIN, + FontWeightContent::ExtraLight => FontWeight::EXTRA_LIGHT, + FontWeightContent::Light => FontWeight::LIGHT, + FontWeightContent::Normal => FontWeight::NORMAL, + FontWeightContent::Medium => FontWeight::MEDIUM, + FontWeightContent::Semibold => FontWeight::SEMIBOLD, + FontWeightContent::Bold => FontWeight::BOLD, + FontWeightContent::ExtraBold => FontWeight::EXTRA_BOLD, + FontWeightContent::Black => FontWeight::BLACK, + } + } +} + #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)] #[serde(default)] pub struct HighlightStyleContent { diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index b642c835e8..e03f4043dc 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -33,7 +33,7 @@ pub use styles::*; pub use themes::*; pub use user_theme::*; -use gpui::{AppContext, Hsla, SharedString}; +use gpui::{AppContext, AssetSource, Hsla, SharedString}; use serde::Deserialize; #[derive(Debug, PartialEq, Clone, Copy, Deserialize)] @@ -51,7 +51,6 @@ impl Appearance { } } -#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum LoadThemes { /// Only load the base theme. /// @@ -59,15 +58,20 @@ pub enum LoadThemes { JustBase, /// Load all of the built-in themes. - All, + All(Box), } pub fn init(themes_to_load: LoadThemes, cx: &mut AppContext) { - cx.set_global(ThemeRegistry::default()); - match themes_to_load { - LoadThemes::JustBase => (), - LoadThemes::All => cx.global_mut::().load_user_themes(), + let (assets, load_user_themes) = match themes_to_load { + LoadThemes::JustBase => (Box::new(()) as Box, false), + LoadThemes::All(assets) => (assets, true), + }; + cx.set_global(ThemeRegistry::new(assets)); + + if load_user_themes { + cx.global_mut::().load_user_themes(); } + ThemeSettings::register(cx); let mut prev_buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size; diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index fc8c282b42..0b7a983cb6 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -149,7 +149,7 @@ fn main() { cx.set_global(client.clone()); zed::init(cx); - theme::init(theme::LoadThemes::All, cx); + theme::init(theme::LoadThemes::All(Box::new(Assets)), cx); project::Project::init(&client, cx); client::init(&client, cx); command_palette::init(cx); diff --git a/script/generate-licenses b/script/generate-licenses index 7c5ac372b1..107da5a0da 100755 --- a/script/generate-licenses +++ b/script/generate-licenses @@ -9,7 +9,7 @@ OUTPUT_FILE=$(pwd)/assets/licenses.md echo -e "# ###### THEME LICENSES ######\n" >> $OUTPUT_FILE echo "Generating theme licenses" -cat crates/theme/src/themes/LICENSES >> $OUTPUT_FILE +cat assets/themes/LICENSES >> $OUTPUT_FILE echo -e "# ###### CODE LICENSES ######\n" >> $OUTPUT_FILE