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
This commit is contained in:
Marshall Bowers 2024-01-27 16:03:04 -05:00 committed by GitHub
parent f7fc4ffbe5
commit 5f1dcb76fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 133 additions and 48 deletions

View file

@ -69,7 +69,7 @@ fn main() {
.unwrap(); .unwrap();
cx.set_global(store); cx.set_global(store);
theme::init(theme::LoadThemes::All, cx); theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
let selector = story_selector; let selector = story_selector;

View file

@ -2,12 +2,13 @@ use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use gpui::{HighlightStyle, SharedString}; use gpui::{AssetSource, HighlightStyle, SharedString};
use refineable::Refineable; use refineable::Refineable;
use crate::{ use crate::{
Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors, Theme, ThemeColors, try_parse_color, Appearance, AppearanceContent, PlayerColor, PlayerColors, StatusColors,
ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily, SyntaxTheme, SystemColors, Theme, ThemeColors, ThemeContent, ThemeFamily, ThemeFamilyContent,
ThemeStyles,
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -17,10 +18,27 @@ pub struct ThemeMeta {
} }
pub struct ThemeRegistry { pub struct ThemeRegistry {
assets: Box<dyn AssetSource>,
themes: HashMap<SharedString, Arc<Theme>>, themes: HashMap<SharedString, Arc<Theme>>,
} }
impl ThemeRegistry { impl ThemeRegistry {
pub fn new(assets: Box<dyn AssetSource>) -> 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<Item = ThemeFamily>) { fn insert_theme_families(&mut self, families: impl IntoIterator<Item = ThemeFamily>) {
for family in families.into_iter() { for family in families.into_iter() {
self.insert_themes(family.themes); self.insert_themes(family.themes);
@ -34,48 +52,78 @@ impl ThemeRegistry {
} }
#[allow(unused)] #[allow(unused)]
fn insert_user_theme_families(&mut self, families: impl IntoIterator<Item = UserThemeFamily>) { fn insert_user_theme_families(
&mut self,
families: impl IntoIterator<Item = ThemeFamilyContent>,
) {
for family in families.into_iter() { for family in families.into_iter() {
self.insert_user_themes(family.themes); self.insert_user_themes(family.themes);
} }
} }
#[allow(unused)] #[allow(unused)]
fn insert_user_themes(&mut self, themes: impl IntoIterator<Item = UserTheme>) { fn insert_user_themes(&mut self, themes: impl IntoIterator<Item = ThemeContent>) {
self.insert_themes(themes.into_iter().map(|user_theme| { self.insert_themes(themes.into_iter().map(|user_theme| {
let mut theme_colors = match user_theme.appearance { let mut theme_colors = match user_theme.appearance {
Appearance::Light => ThemeColors::light(), AppearanceContent::Light => ThemeColors::light(),
Appearance::Dark => ThemeColors::dark(), 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 { let mut status_colors = match user_theme.appearance {
Appearance::Light => StatusColors::light(), AppearanceContent::Light => StatusColors::light(),
Appearance::Dark => StatusColors::dark(), 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 { let mut player_colors = match user_theme.appearance {
Appearance::Light => PlayerColors::light(), AppearanceContent::Light => PlayerColors::light(),
Appearance::Dark => PlayerColors::dark(), AppearanceContent::Dark => PlayerColors::dark(),
}; };
if let Some(player_colors_from_theme) = user_theme.styles.player { if !user_theme.style.players.is_empty() {
player_colors = player_colors_from_theme; 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 { let mut syntax_colors = match user_theme.appearance {
Appearance::Light => SyntaxTheme::light(), AppearanceContent::Light => SyntaxTheme::light(),
Appearance::Dark => SyntaxTheme::dark(), AppearanceContent::Dark => SyntaxTheme::dark(),
}; };
if let Some(user_syntax) = user_theme.styles.syntax { if !user_theme.style.syntax.is_empty() {
syntax_colors.highlights = user_syntax syntax_colors.highlights = user_theme
.highlights .style
.syntax
.iter() .iter()
.map(|(syntax_token, highlight)| { .map(|(syntax_token, highlight)| {
( (
syntax_token.clone(), syntax_token.clone(),
HighlightStyle { 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_style: highlight.font_style.map(Into::into),
font_weight: highlight.font_weight.map(Into::into), font_weight: highlight.font_weight.map(Into::into),
..Default::default() ..Default::default()
@ -88,7 +136,10 @@ impl ThemeRegistry {
Theme { Theme {
id: uuid::Uuid::new_v4().to_string(), id: uuid::Uuid::new_v4().to_string(),
name: user_theme.name.into(), name: user_theme.name.into(),
appearance: user_theme.appearance, appearance: match user_theme.appearance {
AppearanceContent::Light => Appearance::Light,
AppearanceContent::Dark => Appearance::Dark,
},
styles: ThemeStyles { styles: ThemeStyles {
system: SystemColors::default(), system: SystemColors::default(),
colors: theme_colors, colors: theme_colors,
@ -124,24 +175,28 @@ impl ThemeRegistry {
} }
pub fn load_user_themes(&mut self) { pub fn load_user_themes(&mut self) {
#[cfg(not(feature = "importing-themes"))] let theme_paths = self
self.insert_user_theme_families(crate::all_user_themes()); .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 { impl Default for ThemeRegistry {
fn default() -> Self { fn default() -> Self {
let mut registry = Self { Self::new(Box::new(()))
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
} }
} }

View file

@ -1,5 +1,5 @@
use anyhow::Result; use anyhow::Result;
use gpui::{HighlightStyle, Hsla}; use gpui::{FontStyle, FontWeight, HighlightStyle, Hsla};
use indexmap::IndexMap; use indexmap::IndexMap;
use palette::FromColor; use palette::FromColor;
use schemars::gen::SchemaGenerator; use schemars::gen::SchemaGenerator;
@ -11,7 +11,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::{StatusColorsRefinement, ThemeColorsRefinement}; use crate::{StatusColorsRefinement, ThemeColorsRefinement};
fn try_parse_color(color: &str) -> Result<Hsla> { pub(crate) fn try_parse_color(color: &str) -> Result<Hsla> {
let rgba = gpui::Rgba::try_from(color)?; let rgba = gpui::Rgba::try_from(color)?;
let rgba = palette::rgb::Srgba::from_components((rgba.r, rgba.g, rgba.b, rgba.a)); let rgba = palette::rgb::Srgba::from_components((rgba.r, rgba.g, rgba.b, rgba.a));
let hsla = palette::Hsla::from_color(rgba); let hsla = palette::Hsla::from_color(rgba);
@ -1171,6 +1171,16 @@ pub enum FontStyleContent {
Oblique, Oblique,
} }
impl From<FontStyleContent> 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)] #[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)]
#[repr(u16)] #[repr(u16)]
pub enum FontWeightContent { pub enum FontWeightContent {
@ -1211,6 +1221,22 @@ impl JsonSchema for FontWeightContent {
} }
} }
impl From<FontWeightContent> 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)] #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
#[serde(default)] #[serde(default)]
pub struct HighlightStyleContent { pub struct HighlightStyleContent {

View file

@ -33,7 +33,7 @@ pub use styles::*;
pub use themes::*; pub use themes::*;
pub use user_theme::*; pub use user_theme::*;
use gpui::{AppContext, Hsla, SharedString}; use gpui::{AppContext, AssetSource, Hsla, SharedString};
use serde::Deserialize; use serde::Deserialize;
#[derive(Debug, PartialEq, Clone, Copy, Deserialize)] #[derive(Debug, PartialEq, Clone, Copy, Deserialize)]
@ -51,7 +51,6 @@ impl Appearance {
} }
} }
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum LoadThemes { pub enum LoadThemes {
/// Only load the base theme. /// Only load the base theme.
/// ///
@ -59,15 +58,20 @@ pub enum LoadThemes {
JustBase, JustBase,
/// Load all of the built-in themes. /// Load all of the built-in themes.
All, All(Box<dyn AssetSource>),
} }
pub fn init(themes_to_load: LoadThemes, cx: &mut AppContext) { pub fn init(themes_to_load: LoadThemes, cx: &mut AppContext) {
cx.set_global(ThemeRegistry::default()); let (assets, load_user_themes) = match themes_to_load {
match themes_to_load { LoadThemes::JustBase => (Box::new(()) as Box<dyn AssetSource>, false),
LoadThemes::JustBase => (), LoadThemes::All(assets) => (assets, true),
LoadThemes::All => cx.global_mut::<ThemeRegistry>().load_user_themes(), };
cx.set_global(ThemeRegistry::new(assets));
if load_user_themes {
cx.global_mut::<ThemeRegistry>().load_user_themes();
} }
ThemeSettings::register(cx); ThemeSettings::register(cx);
let mut prev_buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size; let mut prev_buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;

View file

@ -149,7 +149,7 @@ fn main() {
cx.set_global(client.clone()); cx.set_global(client.clone());
zed::init(cx); zed::init(cx);
theme::init(theme::LoadThemes::All, cx); theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
project::Project::init(&client, cx); project::Project::init(&client, cx);
client::init(&client, cx); client::init(&client, cx);
command_palette::init(cx); command_palette::init(cx);

View file

@ -9,7 +9,7 @@ OUTPUT_FILE=$(pwd)/assets/licenses.md
echo -e "# ###### THEME LICENSES ######\n" >> $OUTPUT_FILE echo -e "# ###### THEME LICENSES ######\n" >> $OUTPUT_FILE
echo "Generating theme licenses" 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 echo -e "# ###### CODE LICENSES ######\n" >> $OUTPUT_FILE