mirror of
https://github.com/zed-industries/zed.git
synced 2024-10-25 16:09:44 +00:00
Start work on allowing variables in themes
This commit is contained in:
parent
5ac0a1985e
commit
92353b6967
9 changed files with 349 additions and 110 deletions
|
@ -14,7 +14,7 @@ name = "Zed"
|
|||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
test-support = ["tempdir", "serde_json", "zrpc/test-support"]
|
||||
test-support = ["tempdir", "zrpc/test-support"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.38"
|
||||
|
@ -41,9 +41,7 @@ rsa = "0.4"
|
|||
rust-embed = "5.9.0"
|
||||
seahash = "4.1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = { version = "1.0.64", features = [
|
||||
"preserve_order",
|
||||
], optional = true }
|
||||
serde_json = { version = "1.0.64", features = ["preserve_order"] }
|
||||
similar = "1.3"
|
||||
simplelog = "0.9"
|
||||
smallvec = { version = "1.6", features = ["union"] }
|
||||
|
|
28
zed/assets/themes/base.toml
Normal file
28
zed/assets/themes/base.toml
Normal file
|
@ -0,0 +1,28 @@
|
|||
[ui]
|
||||
background = "$elevation_1"
|
||||
tab_background = "$elevation_2"
|
||||
tab_background_active = "$elevation_3"
|
||||
tab_text = "$text_dull"
|
||||
tab_text_active = "$text_bright"
|
||||
tab_border = 0x000000
|
||||
tab_icon_close = 0x383839
|
||||
tab_icon_dirty = 0x556de8
|
||||
tab_icon_conflict = 0xe45349
|
||||
modal_background = "$elevation_4"
|
||||
modal_match_background = 0x424344
|
||||
modal_match_background_active = 0x094771
|
||||
modal_match_border = 0x000000
|
||||
modal_match_text = 0xcccccc
|
||||
modal_match_text_highlight = 0x18a3ff
|
||||
|
||||
[editor]
|
||||
background = "$elevation_3"
|
||||
gutter_background = "$elevation_3"
|
||||
active_line_background = "$elevation_4"
|
||||
line_number = "$text_dull"
|
||||
line_number_active = "$text_bright"
|
||||
default_text = "$text_normal"
|
||||
replicas = [
|
||||
{ selection = 0x264f78, cursor = "$text_bright" },
|
||||
{ selection = 0x504f31, cursor = 0xfcf154 },
|
||||
]
|
|
@ -1,30 +1,13 @@
|
|||
[ui]
|
||||
tab_background = 0x131415
|
||||
tab_background_active = 0x1c1d1e
|
||||
tab_text = 0x5a5a5b
|
||||
tab_text_active = 0xffffff
|
||||
tab_border = 0x000000
|
||||
tab_icon_close = 0x383839
|
||||
tab_icon_dirty = 0x556de8
|
||||
tab_icon_conflict = 0xe45349
|
||||
modal_background = 0x3a3b3c
|
||||
modal_match_background = 0x424344
|
||||
modal_match_background_active = 0x094771
|
||||
modal_match_border = 0x000000
|
||||
modal_match_text = 0xcccccc
|
||||
modal_match_text_highlight = 0x18a3ff
|
||||
extends = "base"
|
||||
|
||||
[editor]
|
||||
background = 0x131415
|
||||
gutter_background = 0x131415
|
||||
active_line_background = 0x1c1d1e
|
||||
line_number = 0x5a5a5b
|
||||
line_number_active = 0xffffff
|
||||
default_text = 0xd4d4d4
|
||||
replicas = [
|
||||
{ selection = 0x264f78, cursor = 0xffffff },
|
||||
{ selection = 0x504f31, cursor = 0xfcf154 },
|
||||
]
|
||||
[variables]
|
||||
elevation_1 = 0x050101
|
||||
elevation_2 = 0x131415
|
||||
elevation_3 = 0x1c1d1e
|
||||
elevation_4 = 0x3a3b3c
|
||||
text_dull = 0x5a5a5b
|
||||
text_bright = 0xffffff
|
||||
text_normal = 0xd4d4d4
|
||||
|
||||
[syntax]
|
||||
keyword = 0xc586c0
|
||||
|
|
|
@ -340,7 +340,7 @@ mod tests {
|
|||
util::RandomCharIter,
|
||||
};
|
||||
use buffer::{History, SelectionGoal};
|
||||
use gpui::MutableAppContext;
|
||||
use gpui::{color::ColorU, MutableAppContext};
|
||||
use rand::{prelude::StdRng, Rng};
|
||||
use std::{env, sync::Arc};
|
||||
use Bias::*;
|
||||
|
@ -652,13 +652,21 @@ mod tests {
|
|||
(function_item name: (identifier) @fn.name)"#,
|
||||
)
|
||||
.unwrap();
|
||||
let theme = Theme::parse(
|
||||
r#"
|
||||
[syntax]
|
||||
"mod.body" = 0xff0000
|
||||
"fn.name" = 0x00ff00"#,
|
||||
)
|
||||
.unwrap();
|
||||
let theme = Theme {
|
||||
syntax: vec![
|
||||
(
|
||||
"mod.body".to_string(),
|
||||
ColorU::from_u32(0xff0000ff),
|
||||
Default::default(),
|
||||
),
|
||||
(
|
||||
"fn.name".to_string(),
|
||||
ColorU::from_u32(0x00ff00ff),
|
||||
Default::default(),
|
||||
),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
let lang = Arc::new(Language {
|
||||
config: LanguageConfig {
|
||||
name: "Test".to_string(),
|
||||
|
@ -742,13 +750,21 @@ mod tests {
|
|||
(function_item name: (identifier) @fn.name)"#,
|
||||
)
|
||||
.unwrap();
|
||||
let theme = Theme::parse(
|
||||
r#"
|
||||
[syntax]
|
||||
"mod.body" = 0xff0000
|
||||
"fn.name" = 0x00ff00"#,
|
||||
)
|
||||
.unwrap();
|
||||
let theme = Theme {
|
||||
syntax: vec![
|
||||
(
|
||||
"mod.body".to_string(),
|
||||
ColorU::from_u32(0xff0000ff),
|
||||
Default::default(),
|
||||
),
|
||||
(
|
||||
"fn.name".to_string(),
|
||||
ColorU::from_u32(0x00ff00ff),
|
||||
Default::default(),
|
||||
),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
let lang = Arc::new(Language {
|
||||
config: LanguageConfig {
|
||||
name: "Test".to_string(),
|
||||
|
|
|
@ -18,9 +18,11 @@ pub mod workspace;
|
|||
pub mod worktree;
|
||||
|
||||
pub use settings::Settings;
|
||||
|
||||
pub struct AppState {
|
||||
pub settings: postage::watch::Receiver<Settings>,
|
||||
pub languages: std::sync::Arc<language::LanguageRegistry>,
|
||||
pub themes: std::sync::Arc<settings::ThemeRegistry>,
|
||||
pub rpc_router: std::sync::Arc<ForegroundRouter>,
|
||||
pub rpc: rpc::Client,
|
||||
pub fs: std::sync::Arc<dyn fs::Fs>,
|
||||
|
|
|
@ -20,13 +20,15 @@ fn main() {
|
|||
|
||||
let app = gpui::App::new(assets::Assets).unwrap();
|
||||
|
||||
let (_, settings) = settings::channel(&app.font_cache()).unwrap();
|
||||
let themes = settings::ThemeRegistry::new(assets::Assets);
|
||||
let (_, settings) = settings::channel_with_themes(&app.font_cache(), &themes).unwrap();
|
||||
let languages = Arc::new(language::LanguageRegistry::new());
|
||||
languages.set_theme(&settings.borrow().theme);
|
||||
|
||||
let mut app_state = AppState {
|
||||
languages: languages.clone(),
|
||||
settings,
|
||||
themes,
|
||||
rpc_router: Arc::new(ForegroundRouter::new()),
|
||||
rpc: rpc::Client::new(languages),
|
||||
fs: Arc::new(RealFs),
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
use super::assets::Assets;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use gpui::{
|
||||
color::ColorU,
|
||||
font_cache::{FamilyId, FontCache},
|
||||
fonts::{Properties as FontProperties, Style as FontStyle, Weight as FontWeight},
|
||||
AssetSource,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use postage::watch;
|
||||
use serde::Deserialize;
|
||||
use serde::{de::value::MapDeserializer, Deserialize};
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
|
@ -26,16 +28,37 @@ pub struct Settings {
|
|||
pub theme: Arc<Theme>,
|
||||
}
|
||||
|
||||
pub struct ThemeRegistry {
|
||||
assets: Box<dyn AssetSource>,
|
||||
themes: Mutex<HashMap<String, Arc<Theme>>>,
|
||||
theme_data: Mutex<HashMap<String, Arc<ThemeToml>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Theme {
|
||||
pub ui: UiTheme,
|
||||
pub editor: EditorTheme,
|
||||
syntax: Vec<(String, ColorU, FontProperties)>,
|
||||
pub syntax: Vec<(String, ColorU, FontProperties)>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ThemeToml {
|
||||
#[serde(default)]
|
||||
extends: Option<String>,
|
||||
#[serde(default)]
|
||||
variables: HashMap<String, Value>,
|
||||
#[serde(default)]
|
||||
ui: HashMap<String, Value>,
|
||||
#[serde(default)]
|
||||
editor: HashMap<String, Value>,
|
||||
#[serde(default)]
|
||||
syntax: HashMap<String, Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct UiTheme {
|
||||
pub background: Color,
|
||||
pub tab_background: Color,
|
||||
pub tab_background_active: Color,
|
||||
pub tab_text: Color,
|
||||
|
@ -81,16 +104,17 @@ pub struct StyleId(u32);
|
|||
|
||||
impl Settings {
|
||||
pub fn new(font_cache: &FontCache) -> Result<Self> {
|
||||
Self::new_with_theme(font_cache, Arc::new(Theme::default()))
|
||||
}
|
||||
|
||||
pub fn new_with_theme(font_cache: &FontCache, theme: Arc<Theme>) -> Result<Self> {
|
||||
Ok(Self {
|
||||
buffer_font_family: font_cache.load_family(&["Fira Code", "Monaco"])?,
|
||||
buffer_font_size: 14.0,
|
||||
tab_size: 4,
|
||||
ui_font_family: font_cache.load_family(&["SF Pro", "Helvetica"])?,
|
||||
ui_font_size: 12.0,
|
||||
theme: Arc::new(
|
||||
Theme::parse(Assets::get("themes/dark.toml").unwrap())
|
||||
.expect("Failed to parse built-in theme"),
|
||||
),
|
||||
theme,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -100,62 +124,104 @@ impl Settings {
|
|||
}
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
pub fn parse(source: impl AsRef<[u8]>) -> Result<Self> {
|
||||
#[derive(Deserialize)]
|
||||
struct ThemeToml {
|
||||
#[serde(default)]
|
||||
ui: UiTheme,
|
||||
#[serde(default)]
|
||||
editor: EditorTheme,
|
||||
#[serde(default)]
|
||||
syntax: HashMap<String, StyleToml>,
|
||||
impl ThemeRegistry {
|
||||
pub fn new(source: impl AssetSource) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
assets: Box::new(source),
|
||||
themes: Default::default(),
|
||||
theme_data: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Result<Arc<Theme>> {
|
||||
if let Some(theme) = self.themes.lock().get(name) {
|
||||
return Ok(theme.clone());
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum StyleToml {
|
||||
Color(Color),
|
||||
Full {
|
||||
color: Option<Color>,
|
||||
weight: Option<toml::Value>,
|
||||
#[serde(default)]
|
||||
italic: bool,
|
||||
},
|
||||
}
|
||||
|
||||
let theme_toml: ThemeToml =
|
||||
toml::from_slice(source.as_ref()).context("failed to parse theme TOML")?;
|
||||
|
||||
let theme_toml = self.load(name)?;
|
||||
let mut syntax = Vec::<(String, ColorU, FontProperties)>::new();
|
||||
for (key, style) in theme_toml.syntax {
|
||||
let (color, weight, italic) = match style {
|
||||
StyleToml::Color(color) => (color, None, false),
|
||||
StyleToml::Full {
|
||||
color,
|
||||
weight,
|
||||
italic,
|
||||
} => (color.unwrap_or(Color::default()), weight, italic),
|
||||
};
|
||||
match syntax.binary_search_by_key(&&key, |e| &e.0) {
|
||||
Ok(i) | Err(i) => {
|
||||
let mut properties = FontProperties::new();
|
||||
properties.weight = deserialize_weight(weight)?;
|
||||
if italic {
|
||||
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;
|
||||
}
|
||||
syntax.insert(i, (key, color.0, properties));
|
||||
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.0, properties));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Theme {
|
||||
ui: theme_toml.ui,
|
||||
editor: theme_toml.editor,
|
||||
let theme = Arc::new(Theme {
|
||||
ui: UiTheme::deserialize(MapDeserializer::new(theme_toml.ui.clone().into_iter()))?,
|
||||
editor: EditorTheme::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<Arc<ThemeToml>> {
|
||||
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_toml: ThemeToml = toml::from_slice(source_code.as_ref())
|
||||
.with_context(|| format!("failed to parse {}.toml", name))?;
|
||||
|
||||
// If this theme extends another base theme, merge in the raw data from the base theme.
|
||||
if let Some(base_name) = theme_toml.extends.as_ref() {
|
||||
let base_theme_toml = self
|
||||
.load(base_name)
|
||||
.with_context(|| format!("failed to load base theme {}", base_name))?;
|
||||
merge_map(&mut theme_toml.ui, &base_theme_toml.ui);
|
||||
merge_map(&mut theme_toml.editor, &base_theme_toml.editor);
|
||||
merge_map(&mut theme_toml.syntax, &base_theme_toml.syntax);
|
||||
merge_map(&mut theme_toml.variables, &base_theme_toml.variables);
|
||||
}
|
||||
|
||||
// Substitute any variable references for their definitions.
|
||||
let values = theme_toml
|
||||
.ui
|
||||
.values_mut()
|
||||
.chain(theme_toml.editor.values_mut())
|
||||
.chain(theme_toml.syntax.values_mut());
|
||||
let mut name_stack = Vec::new();
|
||||
for value in values {
|
||||
name_stack.clear();
|
||||
evaluate_variables(value, &theme_toml.variables, &mut name_stack)?;
|
||||
}
|
||||
|
||||
let result = Arc::new(theme_toml);
|
||||
self.theme_data
|
||||
.lock()
|
||||
.insert(name.to_string(), result.clone());
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
pub fn syntax_style(&self, id: StyleId) -> (ColorU, FontProperties) {
|
||||
self.syntax.get(id.0 as usize).map_or(
|
||||
(self.editor.default_text.0, FontProperties::new()),
|
||||
|
@ -221,13 +287,19 @@ impl Default for StyleId {
|
|||
}
|
||||
}
|
||||
|
||||
impl Color {
|
||||
fn from_u32(rgba: u32) -> Self {
|
||||
Self(ColorU::from_u32(rgba))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Color {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let rgba_value = u32::deserialize(deserializer)?;
|
||||
Ok(Self(ColorU::from_u32((rgba_value << 8) + 0xFF)))
|
||||
let rgb = u32::deserialize(deserializer)?;
|
||||
Ok(Self::from_u32((rgb << 8) + 0xFF))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -268,11 +340,25 @@ pub fn channel(
|
|||
Ok(watch::channel_with(Settings::new(font_cache)?))
|
||||
}
|
||||
|
||||
fn deserialize_weight(weight: Option<toml::Value>) -> Result<FontWeight> {
|
||||
match &weight {
|
||||
pub fn channel_with_themes(
|
||||
font_cache: &FontCache,
|
||||
themes: &ThemeRegistry,
|
||||
) -> Result<(watch::Sender<Settings>, watch::Receiver<Settings>)> {
|
||||
Ok(watch::channel_with(Settings::new_with_theme(
|
||||
font_cache,
|
||||
themes.get("dark").expect("failed to load default theme"),
|
||||
)?))
|
||||
}
|
||||
|
||||
fn deserialize_weight(weight: Option<&Value>) -> Result<FontWeight> {
|
||||
match weight {
|
||||
None => return Ok(FontWeight::NORMAL),
|
||||
Some(toml::Value::Integer(i)) => return Ok(FontWeight(*i as f32)),
|
||||
Some(toml::Value::String(s)) => match s.as_str() {
|
||||
Some(Value::Number(number)) => {
|
||||
if let Some(weight) = number.as_f64() {
|
||||
return Ok(FontWeight(weight as f32));
|
||||
}
|
||||
}
|
||||
Some(Value::String(s)) => match s.as_str() {
|
||||
"normal" => return Ok(FontWeight::NORMAL),
|
||||
"bold" => return Ok(FontWeight::BOLD),
|
||||
"light" => return Ok(FontWeight::LIGHT),
|
||||
|
@ -284,13 +370,70 @@ fn deserialize_weight(weight: Option<toml::Value>) -> Result<FontWeight> {
|
|||
Err(anyhow!("Invalid weight {}", weight.unwrap()))
|
||||
}
|
||||
|
||||
fn evaluate_variables(
|
||||
expr: &mut Value,
|
||||
variables: &HashMap<String, Value>,
|
||||
stack: &mut Vec<String>,
|
||||
) -> Result<()> {
|
||||
match expr {
|
||||
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() {
|
||||
*expr = definition;
|
||||
evaluate_variables(expr, 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
|
||||
}
|
||||
|
||||
fn merge_map(left: &mut HashMap<String, Value>, right: &HashMap<String, Value>) {
|
||||
for (name, value) in right {
|
||||
if !left.contains_key(name) {
|
||||
left.insert(name.clone(), value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_theme() {
|
||||
let theme = Theme::parse(
|
||||
fn test_parse_simple_theme() {
|
||||
let assets = TestAssets(&[(
|
||||
"themes/my-theme.toml",
|
||||
r#"
|
||||
[ui]
|
||||
tab_background_active = 0x100000
|
||||
|
@ -304,8 +447,10 @@ mod tests {
|
|||
"alpha.one" = {color = 0x112233, weight = "bold"}
|
||||
"gamma.three" = {weight = "light", italic = true}
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
)]);
|
||||
|
||||
let registry = ThemeRegistry::new(assets);
|
||||
let theme = registry.get("my-theme").unwrap();
|
||||
|
||||
assert_eq!(theme.ui.tab_background_active, ColorU::from_u32(0x100000ff));
|
||||
assert_eq!(theme.editor.background, ColorU::from_u32(0x00ed00ff));
|
||||
|
@ -334,9 +479,53 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_extended_theme() {
|
||||
let assets = TestAssets(&[
|
||||
(
|
||||
"themes/base.toml",
|
||||
r#"
|
||||
[ui]
|
||||
tab_background = 0x111111
|
||||
tab_text = "$variable_1"
|
||||
|
||||
[editor]
|
||||
background = 0x222222
|
||||
default_text = "$variable_2"
|
||||
"#,
|
||||
),
|
||||
(
|
||||
"themes/light.toml",
|
||||
r#"
|
||||
extends = "base"
|
||||
|
||||
[variables]
|
||||
variable_1 = 0x333333
|
||||
variable_2 = 0x444444
|
||||
|
||||
[ui]
|
||||
tab_background = 0x555555
|
||||
|
||||
[editor]
|
||||
background = 0x666666
|
||||
"#,
|
||||
),
|
||||
]);
|
||||
|
||||
let registry = ThemeRegistry::new(assets);
|
||||
let theme = registry.get("light").unwrap();
|
||||
|
||||
assert_eq!(theme.ui.tab_background, ColorU::from_u32(0x555555ff));
|
||||
assert_eq!(theme.ui.tab_text, ColorU::from_u32(0x333333ff));
|
||||
assert_eq!(theme.editor.background, ColorU::from_u32(0x666666ff));
|
||||
assert_eq!(theme.editor.default_text, ColorU::from_u32(0x444444ff));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_empty_theme() {
|
||||
Theme::parse("").unwrap();
|
||||
let assets = TestAssets(&[("themes/my-theme.toml", "")]);
|
||||
let registry = ThemeRegistry::new(assets);
|
||||
registry.get("my-theme").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -371,4 +560,16 @@ mod tests {
|
|||
Some("variable.builtin")
|
||||
);
|
||||
}
|
||||
|
||||
struct TestAssets(&'static [(&'static str, &'static str)]);
|
||||
|
||||
impl AssetSource for TestAssets {
|
||||
fn load(&self, path: &str) -> Result<std::borrow::Cow<[u8]>> {
|
||||
if let Some(row) = self.0.iter().find(|e| e.0 == path) {
|
||||
Ok(row.1.as_bytes().into())
|
||||
} else {
|
||||
Err(anyhow!("no such path {}", path))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
use crate::{fs::RealFs, language::LanguageRegistry, rpc, settings, time::ReplicaId, AppState};
|
||||
use crate::{
|
||||
fs::RealFs,
|
||||
language::LanguageRegistry,
|
||||
rpc,
|
||||
settings::{self, ThemeRegistry},
|
||||
time::ReplicaId,
|
||||
AppState,
|
||||
};
|
||||
use gpui::{AppContext, Entity, ModelHandle};
|
||||
use smol::channel;
|
||||
use std::{
|
||||
|
@ -149,8 +156,10 @@ fn write_tree(path: &Path, tree: serde_json::Value) {
|
|||
pub fn build_app_state(cx: &AppContext) -> Arc<AppState> {
|
||||
let settings = settings::channel(&cx.font_cache()).unwrap().1;
|
||||
let languages = Arc::new(LanguageRegistry::new());
|
||||
let themes = ThemeRegistry::new(());
|
||||
Arc::new(AppState {
|
||||
settings,
|
||||
themes,
|
||||
languages: languages.clone(),
|
||||
rpc_router: Arc::new(ForegroundRouter::new()),
|
||||
rpc: rpc::Client::new(languages),
|
||||
|
|
|
@ -887,7 +887,7 @@ impl View for Workspace {
|
|||
.with_children(self.modal.as_ref().map(|m| ChildView::new(m.id()).boxed()))
|
||||
.boxed(),
|
||||
)
|
||||
.with_background_color(settings.theme.editor.background)
|
||||
.with_background_color(settings.theme.ui.background)
|
||||
.named("workspace")
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue