From ca9862fff181bb45e64f84902e1073ddb3ebd7aa Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 3 Aug 2021 18:51:06 -0600 Subject: [PATCH] Start on new theme::ThemeRegistry Co-Authored-By: Max Brunsfeld --- zed/src/theme.rs | 304 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 301 insertions(+), 3 deletions(-) diff --git a/zed/src/theme.rs b/zed/src/theme.rs index 1a733ff3a6..ca6aac18a9 100644 --- a/zed/src/theme.rs +++ b/zed/src/theme.rs @@ -1,7 +1,21 @@ -use gpui::color::Color; -use gpui::elements::{ContainerStyle, LabelStyle}; -use gpui::fonts::Properties as FontProperties; +use anyhow::{anyhow, Context, Result}; +use gpui::{ + color::Color, + elements::{ContainerStyle, LabelStyle}, + fonts::Properties as FontProperties, + AssetSource, +}; +use json::{Map, Value}; +use parking_lot::Mutex; use serde::Deserialize; +use serde_json as json; +use std::{cmp::Ordering, collections::HashMap, sync::Arc}; + +pub struct ThemeRegistry { + assets: Box, + themes: Mutex>>, + theme_data: Mutex>>>, +} #[derive(Debug, Default)] pub struct Theme { @@ -78,3 +92,287 @@ impl Default for Editor { } } } + +impl ThemeRegistry { + pub fn new(source: impl AssetSource) -> Arc { + Arc::new(Self { + assets: Box::new(source), + themes: Default::default(), + theme_data: Default::default(), + }) + } + + pub fn list(&self) -> impl Iterator { + self.assets.list("themes/").into_iter().filter_map(|path| { + let filename = path.strip_prefix("themes/")?; + let theme_name = filename.strip_suffix(".toml")?; + if theme_name.starts_with('_') { + None + } else { + Some(theme_name.to_string()) + } + }) + } + + pub fn get(&self, name: &str) -> Result> { + todo!() + // if let Some(theme) = self.themes.lock().get(name) { + // return Ok(theme.clone()); + // } + + // let theme_toml = self.load(name)?; + // let mut syntax = Vec::<(String, Color, FontProperties)>::new(); + // for (key, style) in theme_toml.syntax.iter() { + // let mut color = Color::default(); + // let mut properties = FontProperties::new(); + // match style { + // Value::Object(object) => { + // if let Some(value) = object.get("color") { + // color = serde_json::from_value(value.clone())?; + // } + // if let Some(Value::Bool(true)) = object.get("italic") { + // properties.style = FontStyle::Italic; + // } + // properties.weight = deserialize_weight(object.get("weight"))?; + // } + // _ => { + // color = serde_json::from_value(style.clone())?; + // } + // } + // match syntax.binary_search_by_key(&key, |e| &e.0) { + // Ok(i) | Err(i) => { + // syntax.insert(i, (key.to_string(), color, properties)); + // } + // } + // } + + // let theme = Arc::new(Theme { + // ui: theme::Ui::deserialize(MapDeserializer::new(theme_toml.ui.clone().into_iter()))?, + // editor: theme::Editor::deserialize(MapDeserializer::new( + // theme_toml.editor.clone().into_iter(), + // ))?, + // syntax, + // }); + + // self.themes.lock().insert(name.to_string(), theme.clone()); + // Ok(theme) + } + + fn load(&self, name: &str) -> Result>> { + if let Some(data) = self.theme_data.lock().get(name) { + return Ok(data.clone()); + } + + let asset_path = format!("themes/{}.toml", name); + let source_code = self + .assets + .load(&asset_path) + .with_context(|| format!("failed to load theme file {}", asset_path))?; + + let mut theme_data: Map = toml::from_slice(source_code.as_ref()) + .with_context(|| format!("failed to parse {}.toml", name))?; + + // If this theme extends another base theme, deeply merge it into the base theme's data + if let Some(base_name) = theme_data + .get("extends") + .and_then(|name| name.as_str()) + .map(str::to_string) + { + let mut base_theme_data = self + .load(&base_name) + .with_context(|| format!("failed to load base theme {}", base_name))? + .as_ref() + .clone(); + deep_merge_json(&mut base_theme_data, theme_data); + theme_data = base_theme_data; + } + + // Evaluate `extends` fields in styles + let mut directives = Vec::new(); + let mut key_path = Vec::new(); + for (key, value) in theme_data.iter() { + if value.is_array() || value.is_object() { + key_path.push(Key::Object(key.clone())); + find_extensions(value, &mut key_path, &mut directives); + key_path.pop(); + } + } + directives.sort_unstable(); + for ExtendDirective { + source_path, + target_path, + } in directives + { + let source = value_at(&mut theme_data, &source_path)?.clone(); + let target = value_at(&mut theme_data, &target_path)?; + if let Value::Object(source_object) = source { + deep_merge_json(target.as_object_mut().unwrap(), source_object); + } + } + + // Evaluate any variables + if let Some((key, variables)) = theme_data.remove_entry("variables") { + if let Some(variables) = variables.as_object() { + for value in theme_data.values_mut() { + evaluate_variables(value, &variables, &mut Vec::new())?; + } + } + theme_data.insert(key, variables); + } + + let result = Arc::new(theme_data); + self.theme_data + .lock() + .insert(name.to_string(), result.clone()); + + Ok(result) + } +} + +fn deep_merge_json(base: &mut Map, extension: Map) { + for (key, extension_value) in extension { + if let Value::Object(extension_object) = extension_value { + if let Some(base_object) = base.get_mut(&key).and_then(|value| value.as_object_mut()) { + deep_merge_json(base_object, extension_object); + } else { + base.insert(key, Value::Object(extension_object)); + } + } else { + base.insert(key, extension_value); + } + } +} + +#[derive(Clone, PartialEq, Eq)] +enum Key { + Array(usize), + Object(String), +} + +#[derive(PartialEq, Eq)] +struct ExtendDirective { + source_path: Vec, + target_path: Vec, +} + +impl Ord for ExtendDirective { + fn cmp(&self, other: &Self) -> Ordering { + if self.target_path.starts_with(&other.source_path) + || other.source_path.starts_with(&self.target_path) + { + Ordering::Less + } else if other.target_path.starts_with(&self.source_path) + || self.source_path.starts_with(&other.target_path) + { + Ordering::Greater + } else { + Ordering::Equal + } + } +} + +impl PartialOrd for ExtendDirective { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +fn find_extensions(value: &Value, key_path: &mut Vec, directives: &mut Vec) { + match value { + Value::Array(vec) => { + for (ix, value) in vec.iter().enumerate() { + key_path.push(Key::Array(ix)); + find_extensions(value, key_path, directives); + key_path.pop(); + } + } + Value::Object(map) => { + for (key, value) in map.iter() { + if key == "extends" { + if let Some(source_path) = value.as_str() { + directives.push(ExtendDirective { + source_path: source_path + .split(".") + .map(|key| Key::Object(key.to_string())) + .collect(), + target_path: key_path.clone(), + }); + } + } else if value.is_array() || value.is_object() { + key_path.push(Key::Object(key.to_string())); + find_extensions(value, key_path, directives); + key_path.pop(); + } + } + } + _ => {} + } +} + +fn value_at<'a>(object: &'a mut Map, key_path: &Vec) -> Result<&'a mut Value> { + let mut key_path = key_path.iter(); + if let Some(Key::Object(first_key)) = key_path.next() { + let mut cur_value = object.get_mut(first_key); + for key in key_path { + if let Some(value) = cur_value { + match key { + Key::Array(ix) => cur_value = value.get_mut(ix), + Key::Object(key) => cur_value = value.get_mut(key), + } + } else { + return Err(anyhow!("invalid key path")); + } + } + cur_value.ok_or_else(|| anyhow!("invalid key path")) + } else { + Err(anyhow!("invalid key path")) + } +} + +fn evaluate_variables( + value: &mut Value, + variables: &Map, + stack: &mut Vec, +) -> Result<()> { + match value { + Value::String(s) => { + if let Some(name) = s.strip_prefix("$") { + if stack.iter().any(|e| e == name) { + Err(anyhow!("variable {} is defined recursively", name))?; + } + if validate_variable_name(name) { + stack.push(name.to_string()); + if let Some(definition) = variables.get(name).cloned() { + *value = definition; + evaluate_variables(value, variables, stack)?; + } + stack.pop(); + } + } + } + Value::Array(a) => { + for value in a.iter_mut() { + evaluate_variables(value, variables, stack)?; + } + } + Value::Object(object) => { + for value in object.values_mut() { + evaluate_variables(value, variables, stack)?; + } + } + _ => {} + } + Ok(()) +} + +fn validate_variable_name(name: &str) -> bool { + let mut chars = name.chars(); + if let Some(first) = chars.next() { + if first.is_alphabetic() || first == '_' { + if chars.all(|c| c.is_alphanumeric() || c == '_') { + return true; + } + } + } + false +}