diff --git a/gpui/src/elements/label.rs b/gpui/src/elements/label.rs index 5c0e7e247f..1ad5d83671 100644 --- a/gpui/src/elements/label.rs +++ b/gpui/src/elements/label.rs @@ -1,6 +1,5 @@ use crate::{ color::Color, - font_cache::FamilyId, fonts::{FontId, TextStyle}, geometry::{ rect::RectF, @@ -8,8 +7,7 @@ use crate::{ }, json::{ToJson, Value}, text_layout::Line, - DebugContext, Element, Event, EventContext, FontCache, LayoutContext, PaintContext, - SizeConstraint, + DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, }; use serde::Deserialize; use serde_json::json; @@ -17,37 +15,32 @@ use smallvec::{smallvec, SmallVec}; pub struct Label { text: String, - family_id: FamilyId, - font_size: f32, style: LabelStyle, highlight_indices: Vec, } -#[derive(Clone, Debug, Default, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct LabelStyle { pub text: TextStyle, pub highlight_text: Option, } -impl Label { - pub fn new(text: String, family_id: FamilyId, font_size: f32) -> Self { - Self { +impl From for LabelStyle { + fn from(text: TextStyle) -> Self { + LabelStyle { text, - family_id, - font_size, - highlight_indices: Default::default(), - style: Default::default(), + highlight_text: None, } } +} - pub fn with_style(mut self, style: &LabelStyle) -> Self { - self.style = style.clone(); - self - } - - pub fn with_default_color(mut self, color: Color) -> Self { - self.style.text.color = color; - self +impl Label { + pub fn new(text: String, style: impl Into) -> Self { + Self { + text, + highlight_indices: Default::default(), + style: style.into(), + } } pub fn with_highlights(mut self, indices: Vec) -> Self { @@ -55,11 +48,8 @@ impl Label { self } - fn compute_runs( - &self, - font_cache: &FontCache, - font_id: FontId, - ) -> SmallVec<[(usize, FontId, Color); 8]> { + fn compute_runs(&self) -> SmallVec<[(usize, FontId, Color); 8]> { + let font_id = self.style.text.font_id; if self.highlight_indices.is_empty() { return smallvec![(self.text.len(), font_id, self.style.text.color)]; } @@ -68,12 +58,7 @@ impl Label { .style .highlight_text .as_ref() - .and_then(|style| { - font_cache - .select_font(self.family_id, &style.font_properties) - .ok() - }) - .unwrap_or(font_id); + .map_or(font_id, |style| style.font_id); let mut highlight_indices = self.highlight_indices.iter().copied().peekable(); let mut runs = SmallVec::new(); @@ -123,18 +108,18 @@ impl Element for Label { constraint: SizeConstraint, cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { - let font_id = cx - .font_cache - .select_font(self.family_id, &self.style.text.font_properties) - .unwrap(); - let runs = self.compute_runs(&cx.font_cache, font_id); - let line = - cx.text_layout_cache - .layout_str(self.text.as_str(), self.font_size, runs.as_slice()); + let runs = self.compute_runs(); + let line = cx.text_layout_cache.layout_str( + self.text.as_str(), + self.style.text.font_size, + runs.as_slice(), + ); let size = vec2f( line.width().max(constraint.min.x()).min(constraint.max.x()), - cx.font_cache.line_height(font_id, self.font_size).ceil(), + cx.font_cache + .line_height(self.style.text.font_id, self.style.text.font_size) + .ceil(), ); (size, line) @@ -169,15 +154,13 @@ impl Element for Label { bounds: RectF, _: &Self::LayoutState, _: &Self::PaintState, - cx: &DebugContext, + _: &DebugContext, ) -> Value { json!({ "type": "Label", "bounds": bounds.to_json(), "text": &self.text, "highlight_indices": self.highlight_indices, - "font_family": cx.font_cache.family_name(self.family_id).unwrap(), - "font_size": self.font_size, "style": self.style.to_json(), }) } @@ -201,48 +184,52 @@ mod tests { #[crate::test(self)] fn test_layout_label_with_highlights(cx: &mut crate::MutableAppContext) { - let menlo = cx.font_cache().load_family(&["Menlo"]).unwrap(); - let menlo_regular = cx - .font_cache() - .select_font(menlo, &FontProperties::new()) - .unwrap(); - let menlo_bold = cx - .font_cache() - .select_font(menlo, FontProperties::new().weight(Weight::BOLD)) - .unwrap(); - let black = Color::black(); - let red = Color::new(255, 0, 0, 255); + let default_style = TextStyle::new( + "Menlo", + 12., + Default::default(), + Color::black(), + cx.font_cache(), + ) + .unwrap(); + let highlight_style = TextStyle::new( + "Menlo", + 12., + *FontProperties::new().weight(Weight::BOLD), + Color::new(255, 0, 0, 255), + cx.font_cache(), + ) + .unwrap(); + let label = Label::new( + ".αβγδε.ⓐⓑⓒⓓⓔ.abcde.".to_string(), + LabelStyle { + text: default_style.clone(), + highlight_text: Some(highlight_style.clone()), + }, + ) + .with_highlights(vec![ + ".α".len(), + ".αβ".len(), + ".αβγδ".len(), + ".αβγδε.ⓐ".len(), + ".αβγδε.ⓐⓑ".len(), + ]); - let label = Label::new(".αβγδε.ⓐⓑⓒⓓⓔ.abcde.".to_string(), menlo, 12.0) - .with_style(&LabelStyle { - text: TextStyle { - color: black, - font_properties: Default::default(), - }, - highlight_text: Some(TextStyle { - color: red, - font_properties: *FontProperties::new().weight(Weight::BOLD), - }), - }) - .with_highlights(vec![ - ".α".len(), - ".αβ".len(), - ".αβγδ".len(), - ".αβγδε.ⓐ".len(), - ".αβγδε.ⓐⓑ".len(), - ]); - - let runs = label.compute_runs(cx.font_cache().as_ref(), menlo_regular); + let runs = label.compute_runs(); assert_eq!( runs.as_slice(), &[ - (".α".len(), menlo_regular, black), - ("βγ".len(), menlo_bold, red), - ("δ".len(), menlo_regular, black), - ("ε".len(), menlo_bold, red), - (".ⓐ".len(), menlo_regular, black), - ("ⓑⓒ".len(), menlo_bold, red), - ("ⓓⓔ.abcde.".len(), menlo_regular, black), + (".α".len(), default_style.font_id, default_style.color), + ("βγ".len(), highlight_style.font_id, highlight_style.color), + ("δ".len(), default_style.font_id, default_style.color), + ("ε".len(), highlight_style.font_id, highlight_style.color), + (".ⓐ".len(), default_style.font_id, default_style.color), + ("ⓑⓒ".len(), highlight_style.font_id, highlight_style.color), + ( + "ⓓⓔ.abcde.".len(), + default_style.font_id, + default_style.color + ), ] ); } diff --git a/gpui/src/elements/text.rs b/gpui/src/elements/text.rs index 0459a68829..6978542071 100644 --- a/gpui/src/elements/text.rs +++ b/gpui/src/elements/text.rs @@ -1,6 +1,5 @@ use crate::{ color::Color, - font_cache::FamilyId, fonts::TextStyle, geometry::{ rect::RectF, @@ -14,8 +13,6 @@ use serde_json::json; pub struct Text { text: String, - family_id: FamilyId, - font_size: f32, style: TextStyle, } @@ -25,18 +22,8 @@ pub struct LayoutState { } impl Text { - pub fn new(text: String, family_id: FamilyId, font_size: f32) -> Self { - Self { - text, - family_id, - font_size, - style: Default::default(), - } - } - - pub fn with_style(mut self, style: &TextStyle) -> Self { - self.style = style.clone(); - self + pub fn new(text: String, style: TextStyle) -> Self { + Self { text, style } } pub fn with_default_color(mut self, color: Color) -> Self { @@ -54,20 +41,17 @@ impl Element for Text { constraint: SizeConstraint, cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { - let font_id = cx - .font_cache - .select_font(self.family_id, &self.style.font_properties) - .unwrap(); - let line_height = cx.font_cache.line_height(font_id, self.font_size); + let font_id = self.style.font_id; + let line_height = cx.font_cache.line_height(font_id, self.style.font_size); - let mut wrapper = cx.font_cache.line_wrapper(font_id, self.font_size); + let mut wrapper = cx.font_cache.line_wrapper(font_id, self.style.font_size); let mut lines = Vec::new(); let mut line_count = 0; let mut max_line_width = 0_f32; for line in self.text.lines() { let shaped_line = cx.text_layout_cache.layout_str( line, - self.font_size, + self.style.font_size, &[(line.len(), font_id, self.style.color)], ); let wrap_boundaries = wrapper @@ -123,14 +107,12 @@ impl Element for Text { bounds: RectF, _: &Self::LayoutState, _: &Self::PaintState, - cx: &DebugContext, + _: &DebugContext, ) -> Value { json!({ - "type": "Label", + "type": "Text", "bounds": bounds.to_json(), "text": &self.text, - "font_family": cx.font_cache.family_name(self.family_id).unwrap(), - "font_size": self.font_size, "style": self.style.to_json(), }) } diff --git a/gpui/src/fonts.rs b/gpui/src/fonts.rs index ab387b65bb..3010e1ada0 100644 --- a/gpui/src/fonts.rs +++ b/gpui/src/fonts.rs @@ -1,32 +1,35 @@ use crate::{ color::Color, json::{json, ToJson}, + FontCache, }; +use anyhow::anyhow; pub use font_kit::{ metrics::Metrics, properties::{Properties, Stretch, Style, Weight}, }; use serde::{de, Deserialize}; use serde_json::Value; +use std::{cell::RefCell, sync::Arc}; #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct FontId(pub usize); pub type GlyphId = u32; -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug)] pub struct TextStyle { pub color: Color, + pub font_family_name: Arc, + pub font_id: FontId, + pub font_size: f32, pub font_properties: Properties, } -impl Default for TextStyle { - fn default() -> Self { - Self { - color: Color::from_u32(0xff0000ff), - font_properties: Default::default(), - } - } +#[derive(Clone, Debug, Default)] +pub struct HighlightStyle { + pub color: Color, + pub font_properties: Properties, } #[allow(non_camel_case_types)] @@ -43,24 +46,117 @@ enum WeightJson { black, } +thread_local! { + static FONT_CACHE: RefCell>> = Default::default(); +} + #[derive(Deserialize)] struct TextStyleJson { + color: Color, + family: String, + weight: Option, + #[serde(default)] + italic: bool, + size: f32, +} + +#[derive(Deserialize)] +struct HighlightStyleJson { color: Color, weight: Option, #[serde(default)] italic: bool, } +impl TextStyle { + pub fn new( + font_family_name: impl Into>, + font_size: f32, + font_properties: Properties, + color: Color, + font_cache: &FontCache, + ) -> anyhow::Result { + let font_family_name = font_family_name.into(); + let family_id = font_cache.load_family(&[&font_family_name])?; + let font_id = font_cache.select_font(family_id, &font_properties)?; + Ok(Self { + color, + font_family_name, + font_id, + font_size, + font_properties, + }) + } + + fn from_json(json: TextStyleJson) -> anyhow::Result { + FONT_CACHE.with(|font_cache| { + if let Some(font_cache) = font_cache.borrow().as_ref() { + let font_properties = properties_from_json(json.weight, json.italic); + Self::new( + json.family, + json.size, + font_properties, + json.color, + font_cache, + ) + } else { + Err(anyhow!( + "TextStyle can only be deserialized within a call to with_font_cache" + )) + } + }) + } +} + +impl HighlightStyle { + fn from_json(json: HighlightStyleJson) -> Self { + let font_properties = properties_from_json(json.weight, json.italic); + Self { + color: json.color, + font_properties, + } + } +} + +impl From for HighlightStyle { + fn from(color: Color) -> Self { + Self { + color, + font_properties: Default::default(), + } + } +} + impl<'de> Deserialize<'de> for TextStyle { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - let json = Value::deserialize(deserializer)?; + Ok(Self::from_json(TextStyleJson::deserialize(deserializer)?) + .map_err(|e| de::Error::custom(e))?) + } +} + +impl ToJson for TextStyle { + fn to_json(&self) -> Value { + json!({ + "color": self.color.to_json(), + "font_family": self.font_family_name.as_ref(), + "font_properties": self.font_properties.to_json(), + }) + } +} + +impl<'de> Deserialize<'de> for HighlightStyle { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let json = serde_json::Value::deserialize(deserializer)?; if json.is_object() { - let style_json: TextStyleJson = - serde_json::from_value(json).map_err(de::Error::custom)?; - Ok(style_json.into()) + Ok(Self::from_json( + serde_json::from_value(json).map_err(de::Error::custom)?, + )) } else { Ok(Self { color: serde_json::from_value(json).map_err(de::Error::custom)?, @@ -70,47 +166,20 @@ impl<'de> Deserialize<'de> for TextStyle { } } -impl From for TextStyle { - fn from(color: Color) -> Self { - Self { - color, - font_properties: Default::default(), - } - } -} - -impl ToJson for TextStyle { - fn to_json(&self) -> Value { - json!({ - "color": self.color.to_json(), - "font_properties": self.font_properties.to_json(), - }) - } -} - -impl Into for TextStyleJson { - fn into(self) -> TextStyle { - let weight = match self.weight.unwrap_or(WeightJson::normal) { - WeightJson::thin => Weight::THIN, - WeightJson::extra_light => Weight::EXTRA_LIGHT, - WeightJson::light => Weight::LIGHT, - WeightJson::normal => Weight::NORMAL, - WeightJson::medium => Weight::MEDIUM, - WeightJson::semibold => Weight::SEMIBOLD, - WeightJson::bold => Weight::BOLD, - WeightJson::extra_bold => Weight::EXTRA_BOLD, - WeightJson::black => Weight::BLACK, - }; - let style = if self.italic { - Style::Italic - } else { - Style::Normal - }; - TextStyle { - color: self.color, - font_properties: *Properties::new().weight(weight).style(style), - } - } +fn properties_from_json(weight: Option, italic: bool) -> Properties { + let weight = match weight.unwrap_or(WeightJson::normal) { + WeightJson::thin => Weight::THIN, + WeightJson::extra_light => Weight::EXTRA_LIGHT, + WeightJson::light => Weight::LIGHT, + WeightJson::normal => Weight::NORMAL, + WeightJson::medium => Weight::MEDIUM, + WeightJson::semibold => Weight::SEMIBOLD, + WeightJson::bold => Weight::BOLD, + WeightJson::extra_bold => Weight::EXTRA_BOLD, + WeightJson::black => Weight::BLACK, + }; + let style = if italic { Style::Italic } else { Style::Normal }; + *Properties::new().weight(weight).style(style) } impl ToJson for Properties { @@ -164,3 +233,15 @@ impl ToJson for Stretch { json!(self.0) } } + +pub fn with_font_cache(font_cache: Arc, callback: F) -> T +where + F: FnOnce() -> T, +{ + FONT_CACHE.with(|cache| { + *cache.borrow_mut() = Some(font_cache); + let result = callback(); + cache.borrow_mut().take(); + result + }) +} diff --git a/server/src/rpc.rs b/server/src/rpc.rs index 1f329f5219..41f5efb51b 100644 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -943,7 +943,7 @@ mod tests { #[gpui::test] async fn test_share_worktree(mut cx_a: TestAppContext, mut cx_b: TestAppContext) { let (window_b, _) = cx_b.add_window(|_| EmptyView); - let settings = settings::channel(&cx_b.font_cache()).unwrap().1; + let settings = cx_b.read(settings::test).1; let lang_registry = Arc::new(LanguageRegistry::new()); // Connect to a server as 2 clients. diff --git a/zed/assets/themes/_base.toml b/zed/assets/themes/_base.toml index 92d3793bb0..06b6346531 100644 --- a/zed/assets/themes/_base.toml +++ b/zed/assets/themes/_base.toml @@ -4,7 +4,7 @@ background = "$surface.0" [workspace.tab] text = "$text.2" padding = { left = 10, right = 10 } -icon_close = "$text.0" +icon_close = "$text.0.color" icon_dirty = "$status.info" icon_conflict = "$status.warn" @@ -17,10 +17,10 @@ text = "$text.0" padding = { left = 10, right = 10 } [workspace.sidebar_icon] -color = "$text.2" +color = "$text.2.color" [workspace.active_sidebar_icon] -color = "$text.0" +color = "$text.0.color" [chat_panel] padding = { top = 10.0, bottom = 10.0, left = 10.0, right = 10.0 } @@ -28,7 +28,7 @@ padding = { top = 10.0, bottom = 10.0, left = 10.0, right = 10.0 } [chat_panel.message] body = "$text.0" sender.margin.right = 10.0 -sender.text = { color = "$text.0", weight = "bold" } +sender.text = { extends = "$text.0", weight = "bold" } timestamp.text = "$text.2" [selector] @@ -41,8 +41,8 @@ shadow = { offset = [0.0, 0.0], blur = 12.0, color = "#00000088" } [selector.item] background = "#424344" -text = "#cccccc" -highlight_text = { color = "#18a3ff", weight = "bold" } +text = "$text.1" +highlight_text = { extends = "$text.base", color = "#18a3ff", weight = "bold" } border = { color = "#000000", width = 1.0 } padding = { top = 6.0, bottom = 6.0, left = 6.0, right = 6.0 } @@ -54,10 +54,12 @@ background = "#094771" background = "$surface.1" gutter_background = "$surface.1" active_line_background = "$surface.2" -line_number = "$text.2" -line_number_active = "$text.0" -text = "$text.1" +line_number = "$text.2.color" +line_number_active = "$text.0.color" replicas = [ - { selection = "#264f78", cursor = "$text.0" }, + { selection = "#264f78", cursor = "$text.0.color" }, { selection = "#504f31", cursor = "#fcf154" }, ] + +[syntax] +default = "$text.1.color" diff --git a/zed/assets/themes/dark.toml b/zed/assets/themes/dark.toml index b019057f65..ce44b87e96 100644 --- a/zed/assets/themes/dark.toml +++ b/zed/assets/themes/dark.toml @@ -6,9 +6,10 @@ extends = "_base" 2 = "#131415" [text] -0 = "#ffffff" -1 = "#b3b3b3" -2 = "#7b7d80" +base = { family = "Helvetica", size = 12.0 } +0 = { extends = "$text.base", color = "#ffffff" } +1 = { extends = "$text.base", color = "#b3b3b3" } +2 = { extends = "$text.base", color = "#7b7d80" } [status] good = "#4fac63" diff --git a/zed/assets/themes/light.toml b/zed/assets/themes/light.toml index 380a5cee01..6702c18f1c 100644 --- a/zed/assets/themes/light.toml +++ b/zed/assets/themes/light.toml @@ -7,9 +7,10 @@ extends = "_base" 3 = "#3a3b3c" [text] -0 = "#acacac" -1 = "#111111" -2 = "#333333" +base = { family = "Inconsolata" } +0 = { extends = "$text.base", color = "#acacac" } +1 = { extends = "$text.base", color = "#111111" } +2 = { extends = "$text.base", color = "#333333" } [status] good = "#4fac63" diff --git a/zed/src/chat_panel.rs b/zed/src/chat_panel.rs index 0573666f9d..2572acc12a 100644 --- a/zed/src/chat_panel.rs +++ b/zed/src/chat_panel.rs @@ -126,10 +126,8 @@ impl ChatPanel { Container::new( Label::new( message.sender.github_login.clone(), - settings.ui_font_family, - settings.ui_font_size, + theme.sender.label.clone(), ) - .with_style(&theme.sender.label) .boxed(), ) .with_style(&theme.sender.container) @@ -139,10 +137,8 @@ impl ChatPanel { Container::new( Label::new( format_timestamp(message.timestamp, now), - settings.ui_font_family, - settings.ui_font_size, + theme.timestamp.label.clone(), ) - .with_style(&theme.timestamp.label) .boxed(), ) .with_style(&theme.timestamp.container) @@ -150,15 +146,7 @@ impl ChatPanel { ) .boxed(), ) - .with_child( - Text::new( - message.body.clone(), - settings.ui_font_family, - settings.ui_font_size, - ) - .with_style(&theme.body) - .boxed(), - ) + .with_child(Text::new(message.body.clone(), theme.body.clone()).boxed()) .boxed() } diff --git a/zed/src/editor.rs b/zed/src/editor.rs index 4aa5b6ce3e..751d687403 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -2418,7 +2418,7 @@ impl Snapshot { } if !line_chunk.is_empty() && !line_exceeded_max_len { - let style = self.theme.highlight_style(style_ix); + let style = self.theme.syntax.highlight_style(style_ix); // Avoid a lookup if the font properties match the previous ones. let font_id = if style.font_properties == prev_font_properties { prev_font_id @@ -2632,19 +2632,14 @@ impl workspace::ItemView for Editor { #[cfg(test)] mod tests { use super::*; - use crate::{ - editor::Point, - language::LanguageRegistry, - settings, - test::{build_settings, sample_text}, - }; + use crate::{editor::Point, language::LanguageRegistry, settings, test::sample_text}; use buffer::History; use unindent::Unindent; #[gpui::test] fn test_selection_with_mouse(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx)); - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let (_, editor) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer, settings, cx) }); @@ -2712,7 +2707,7 @@ mod tests { #[gpui::test] fn test_canceling_pending_selection(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx)); - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer, settings, cx) }); @@ -2746,7 +2741,7 @@ mod tests { #[gpui::test] fn test_cancel(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx)); - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer, settings, cx) }); @@ -2792,7 +2787,7 @@ mod tests { let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx)); - let settings = settings::channel(&font_cache).unwrap().1; + let settings = settings::test(&cx).1; let (_, editor) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer.clone(), settings.clone(), cx) }); @@ -2838,7 +2833,7 @@ mod tests { cx, ) }); - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer.clone(), settings, cx) }); @@ -2906,7 +2901,7 @@ mod tests { #[gpui::test] fn test_move_cursor(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6), cx)); - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer.clone(), settings, cx) }); @@ -2983,7 +2978,7 @@ mod tests { #[gpui::test] fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx)); - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer.clone(), settings, cx) }); @@ -3041,7 +3036,7 @@ mod tests { #[gpui::test] fn test_move_cursor_different_line_lengths(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx)); - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer.clone(), settings, cx) }); @@ -3072,7 +3067,7 @@ mod tests { #[gpui::test] fn test_beginning_end_of_line(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "abc\n def", cx)); - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer, settings, cx) }); @@ -3217,7 +3212,7 @@ mod tests { fn test_prev_next_word_boundary(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "use std::str::{foo, bar}\n\n {baz.qux()}", cx)); - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer, settings, cx) }); @@ -3405,7 +3400,7 @@ mod tests { fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "use one::{\n two::three::four::five\n};", cx)); - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer, settings, cx) }); @@ -3467,7 +3462,7 @@ mod tests { cx, ) }); - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer.clone(), settings, cx) }); @@ -3503,7 +3498,7 @@ mod tests { cx, ) }); - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer.clone(), settings, cx) }); @@ -3532,7 +3527,7 @@ mod tests { #[gpui::test] fn test_delete_line(cx: &mut gpui::MutableAppContext) { - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndef\nghi\n", cx)); let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer, settings, cx) @@ -3558,7 +3553,7 @@ mod tests { ); }); - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndef\nghi\n", cx)); let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer, settings, cx) @@ -3577,7 +3572,7 @@ mod tests { #[gpui::test] fn test_duplicate_line(cx: &mut gpui::MutableAppContext) { - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndef\nghi\n", cx)); let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer, settings, cx) @@ -3606,7 +3601,7 @@ mod tests { ); }); - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndef\nghi\n", cx)); let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer, settings, cx) @@ -3634,7 +3629,7 @@ mod tests { #[gpui::test] fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) { - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(10, 5), cx)); let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer, settings, cx) @@ -3719,7 +3714,7 @@ mod tests { #[gpui::test] fn test_clipboard(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "one two three four five six ", cx)); - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let view = cx .add_window(Default::default(), |cx| { Editor::for_buffer(buffer.clone(), settings, cx) @@ -3854,7 +3849,7 @@ mod tests { #[gpui::test] fn test_select_all(cx: &mut gpui::MutableAppContext) { let buffer = cx.add_model(|cx| Buffer::new(0, "abc\nde\nfgh", cx)); - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer, settings, cx) }); @@ -3869,7 +3864,7 @@ mod tests { #[gpui::test] fn test_select_line(cx: &mut gpui::MutableAppContext) { - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(6, 5), cx)); let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer, settings, cx) @@ -3917,7 +3912,7 @@ mod tests { #[gpui::test] fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) { - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(9, 5), cx)); let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer, settings, cx) @@ -3984,7 +3979,7 @@ mod tests { #[gpui::test] fn test_add_selection_above_below(cx: &mut gpui::MutableAppContext) { - let settings = settings::channel(&cx.font_cache()).unwrap().1; + let settings = settings::test(&cx).1; let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndefghi\n\njk\nlmno\n", cx)); let (_, view) = cx.add_window(Default::default(), |cx| { Editor::for_buffer(buffer, settings, cx) @@ -4159,7 +4154,7 @@ mod tests { #[gpui::test] async fn test_select_larger_smaller_syntax_node(mut cx: gpui::TestAppContext) { - let settings = cx.read(build_settings); + let settings = cx.read(settings::test).1; let languages = LanguageRegistry::new(); let lang = languages.select_language("z.rs"); let text = r#" diff --git a/zed/src/editor/display_map.rs b/zed/src/editor/display_map.rs index e118f3202d..e5f18b56db 100644 --- a/zed/src/editor/display_map.rs +++ b/zed/src/editor/display_map.rs @@ -343,8 +343,8 @@ mod tests { use crate::{ editor::movement, language::{Language, LanguageConfig}, - settings::Theme, test::*, + theme::SyntaxTheme, util::RandomCharIter, }; use buffer::{History, SelectionGoal}; @@ -366,7 +366,7 @@ mod tests { tab_size: rng.gen_range(1..=4), buffer_font_family: font_cache.load_family(&["Helvetica"]).unwrap(), buffer_font_size: 14.0, - ..Settings::new(&font_cache).unwrap() + ..cx.read(Settings::test) }; let max_wrap_width = 300.0; let mut wrap_width = if rng.gen_bool(0.1) { @@ -535,7 +535,7 @@ mod tests { buffer_font_size: 12.0, ui_font_size: 12.0, tab_size: 4, - theme: Arc::new(Theme::default()), + ..cx.read(Settings::test) }; let wrap_width = Some(64.); @@ -606,7 +606,10 @@ mod tests { let map = cx.add_model(|cx| { DisplayMap::new( buffer.clone(), - Settings::new(cx.font_cache()).unwrap().with_tab_size(4), + Settings { + tab_size: 4, + ..Settings::test(cx) + }, None, cx, ) @@ -660,13 +663,13 @@ mod tests { (function_item name: (identifier) @fn.name)"#, ) .unwrap(); - let theme = Theme { - syntax: vec![ + let theme = SyntaxTheme::new( + Default::default(), + vec![ ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()), ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()), ], - ..Default::default() - }; + ); let lang = Arc::new(Language { config: LanguageConfig { name: "Test".to_string(), @@ -688,7 +691,10 @@ mod tests { let map = cx.add_model(|cx| { DisplayMap::new( buffer, - Settings::new(cx.font_cache()).unwrap().with_tab_size(2), + Settings { + tab_size: 2, + ..Settings::test(cx) + }, None, cx, ) @@ -750,13 +756,13 @@ mod tests { (function_item name: (identifier) @fn.name)"#, ) .unwrap(); - let theme = Theme { - syntax: vec![ + let theme = SyntaxTheme::new( + Default::default(), + vec![ ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()), ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()), ], - ..Default::default() - }; + ); let lang = Arc::new(Language { config: LanguageConfig { name: "Test".to_string(), @@ -780,7 +786,7 @@ mod tests { tab_size: 4, buffer_font_family: font_cache.load_family(&["Courier"]).unwrap(), buffer_font_size: 16.0, - ..Settings::new(&font_cache).unwrap() + ..cx.read(Settings::test) }; let map = cx.add_model(|cx| DisplayMap::new(buffer, settings, Some(40.0), cx)); assert_eq!( @@ -820,7 +826,10 @@ mod tests { let map = cx.add_model(|cx| { DisplayMap::new( buffer.clone(), - Settings::new(cx.font_cache()).unwrap().with_tab_size(4), + Settings { + tab_size: 4, + ..Settings::test(cx) + }, None, cx, ) @@ -861,7 +870,10 @@ mod tests { let map = cx.add_model(|cx| { DisplayMap::new( buffer.clone(), - Settings::new(cx.font_cache()).unwrap().with_tab_size(4), + Settings { + tab_size: 4, + ..Settings::test(cx) + }, None, cx, ) @@ -925,7 +937,10 @@ mod tests { let map = cx.add_model(|cx| { DisplayMap::new( buffer.clone(), - Settings::new(cx.font_cache()).unwrap().with_tab_size(4), + Settings { + tab_size: 4, + ..Settings::test(cx) + }, None, cx, ) @@ -939,7 +954,7 @@ mod tests { fn highlighted_chunks<'a>( rows: Range, map: &ModelHandle, - theme: &'a Theme, + theme: &'a SyntaxTheme, cx: &mut MutableAppContext, ) -> Vec<(String, Option<&'a str>)> { let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx)); diff --git a/zed/src/editor/display_map/wrap_map.rs b/zed/src/editor/display_map/wrap_map.rs index 3ba9690c64..8720e39979 100644 --- a/zed/src/editor/display_map/wrap_map.rs +++ b/zed/src/editor/display_map/wrap_map.rs @@ -921,7 +921,7 @@ mod tests { tab_size: rng.gen_range(1..=4), buffer_font_family: font_cache.load_family(&["Helvetica"]).unwrap(), buffer_font_size: 14.0, - ..Settings::new(&font_cache).unwrap() + ..cx.read(Settings::test) }; log::info!("Tab size: {}", settings.tab_size); log::info!("Wrap width: {:?}", wrap_width); diff --git a/zed/src/editor/movement.rs b/zed/src/editor/movement.rs index e42a63f763..0e244995e7 100644 --- a/zed/src/editor/movement.rs +++ b/zed/src/editor/movement.rs @@ -182,12 +182,12 @@ mod tests { use super::*; use crate::{ editor::{display_map::DisplayMap, Buffer}, - test::build_app_state, + test::test_app_state, }; #[gpui::test] fn test_prev_next_word_boundary_multibyte(cx: &mut gpui::MutableAppContext) { - let settings = build_app_state(cx).settings.borrow().clone(); + let settings = test_app_state(cx).settings.borrow().clone(); let buffer = cx.add_model(|cx| Buffer::new(0, "a bcΔ defγ", cx)); let display_map = cx.add_model(|cx| DisplayMap::new(buffer, settings, None, cx)); let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx)); diff --git a/zed/src/file_finder.rs b/zed/src/file_finder.rs index 50ddc48038..76c2f71dc8 100644 --- a/zed/src/file_finder.rs +++ b/zed/src/file_finder.rs @@ -117,13 +117,7 @@ impl FileFinder { if self.matches.is_empty() { let settings = self.settings.borrow(); return Container::new( - Label::new( - "No matches".into(), - settings.ui_font_family, - settings.ui_font_size, - ) - .with_style(&settings.theme.selector.label) - .boxed(), + Label::new("No matches".into(), settings.theme.selector.label.clone()).boxed(), ) .with_margin_top(6.0) .named("empty matches"); @@ -184,24 +178,14 @@ impl FileFinder { 1.0, Flex::column() .with_child( - Label::new( - file_name.to_string(), - settings.ui_font_family, - settings.ui_font_size, - ) - .with_style(&style.label) - .with_highlights(file_name_positions) - .boxed(), + Label::new(file_name.to_string(), style.label.clone()) + .with_highlights(file_name_positions) + .boxed(), ) .with_child( - Label::new( - full_path, - settings.ui_font_family, - settings.ui_font_size, - ) - .with_style(&style.label) - .with_highlights(full_path_positions) - .boxed(), + Label::new(full_path, style.label.clone()) + .with_highlights(full_path_positions) + .boxed(), ) .boxed(), ) @@ -438,7 +422,7 @@ mod tests { use crate::{ editor::{self, Insert}, fs::FakeFs, - test::{build_app_state, temp_tree}, + test::{temp_tree, test_app_state}, workspace::Workspace, }; use serde_json::json; @@ -456,7 +440,7 @@ mod tests { editor::init(cx); }); - let app_state = cx.update(build_app_state); + let app_state = cx.update(test_app_state); let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); workspace .update(&mut cx, |workspace, cx| { @@ -516,7 +500,7 @@ mod tests { ) .await; - let mut app_state = cx.update(build_app_state); + let mut app_state = cx.update(test_app_state); Arc::get_mut(&mut app_state).unwrap().fs = fs; let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); @@ -578,7 +562,7 @@ mod tests { fs::create_dir(&dir_path).unwrap(); fs::write(&file_path, "").unwrap(); - let app_state = cx.update(build_app_state); + let app_state = cx.update(test_app_state); let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); workspace .update(&mut cx, |workspace, cx| { @@ -625,7 +609,7 @@ mod tests { "dir2": { "a.txt": "" } })); - let app_state = cx.update(build_app_state); + let app_state = cx.update(test_app_state); let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); workspace diff --git a/zed/src/language.rs b/zed/src/language.rs index 886befe595..f6342bf254 100644 --- a/zed/src/language.rs +++ b/zed/src/language.rs @@ -1,4 +1,4 @@ -use crate::settings::{HighlightMap, Theme}; +use crate::{settings::HighlightMap, theme::SyntaxTheme}; use parking_lot::Mutex; use rust_embed::RustEmbed; use serde::Deserialize; @@ -39,7 +39,7 @@ impl Language { self.highlight_map.lock().clone() } - pub fn set_theme(&self, theme: &Theme) { + pub fn set_theme(&self, theme: &SyntaxTheme) { *self.highlight_map.lock() = HighlightMap::new(self.highlight_query.capture_names(), theme); } } @@ -61,7 +61,7 @@ impl LanguageRegistry { } } - pub fn set_theme(&self, theme: &Theme) { + pub fn set_theme(&self, theme: &SyntaxTheme) { for language in &self.languages { language.set_theme(theme); } diff --git a/zed/src/main.rs b/zed/src/main.rs index 2258f74686..d1361870b8 100644 --- a/zed/src/main.rs +++ b/zed/src/main.rs @@ -22,11 +22,10 @@ fn main() { let app = gpui::App::new(assets::Assets).unwrap(); - let themes = settings::ThemeRegistry::new(assets::Assets); - let (settings_tx, settings) = - settings::channel_with_themes(&app.font_cache(), &themes).unwrap(); + let themes = settings::ThemeRegistry::new(assets::Assets, app.font_cache()); + let (settings_tx, settings) = settings::channel(&app.font_cache(), &themes).unwrap(); let languages = Arc::new(language::LanguageRegistry::new()); - languages.set_theme(&settings.borrow().theme); + languages.set_theme(&settings.borrow().theme.syntax); app.run(move |cx| { let rpc = rpc::Client::new(); diff --git a/zed/src/settings.rs b/zed/src/settings.rs index a6b9e667c1..dba30749b4 100644 --- a/zed/src/settings.rs +++ b/zed/src/settings.rs @@ -17,11 +17,27 @@ pub struct Settings { } impl Settings { - pub fn new(font_cache: &FontCache) -> Result { - Self::new_with_theme(font_cache, Arc::new(Theme::default())) + #[cfg(any(test, feature = "test-support"))] + pub fn test(cx: &gpui::AppContext) -> Self { + lazy_static::lazy_static! { + static ref DEFAULT_THEME: parking_lot::Mutex>> = Default::default(); + } + + let mut theme_guard = DEFAULT_THEME.lock(); + let theme = if let Some(theme) = theme_guard.as_ref() { + theme.clone() + } else { + let theme = ThemeRegistry::new(crate::assets::Assets, cx.font_cache().clone()) + .get(DEFAULT_THEME_NAME) + .expect("failed to load default theme in tests"); + *theme_guard = Some(theme.clone()); + theme + }; + + Self::new(cx.font_cache(), theme).unwrap() } - pub fn new_with_theme(font_cache: &FontCache, theme: Arc) -> Result { + pub fn new(font_cache: &FontCache, theme: Arc) -> Result { Ok(Self { buffer_font_family: font_cache.load_family(&["Fira Code", "Monaco"])?, buffer_font_size: 14.0, @@ -38,13 +54,12 @@ impl Settings { } } -pub fn channel( - font_cache: &FontCache, -) -> Result<(watch::Sender, watch::Receiver)> { - Ok(watch::channel_with(Settings::new(font_cache)?)) +#[cfg(any(test, feature = "test-support"))] +pub fn test(cx: &gpui::AppContext) -> (watch::Sender, watch::Receiver) { + watch::channel_with(Settings::test(cx)) } -pub fn channel_with_themes( +pub fn channel( font_cache: &FontCache, themes: &ThemeRegistry, ) -> Result<(watch::Sender, watch::Receiver)> { @@ -54,7 +69,5 @@ pub fn channel_with_themes( panic!("failed to deserialize default theme: {:?}", err) } }; - Ok(watch::channel_with(Settings::new_with_theme( - font_cache, theme, - )?)) + Ok(watch::channel_with(Settings::new(font_cache, theme)?)) } diff --git a/zed/src/test.rs b/zed/src/test.rs index f406df1946..d8b30afa69 100644 --- a/zed/src/test.rs +++ b/zed/src/test.rs @@ -6,11 +6,10 @@ use crate::{ settings::{self, ThemeRegistry}, time::ReplicaId, user::UserStore, - AppState, Settings, + AppState, }; -use gpui::{AppContext, Entity, ModelHandle, MutableAppContext}; +use gpui::{Entity, ModelHandle, MutableAppContext}; use parking_lot::Mutex; -use postage::watch; use smol::channel; use std::{ marker::PhantomData, @@ -156,14 +155,10 @@ fn write_tree(path: &Path, tree: serde_json::Value) { } } -pub fn build_settings(cx: &AppContext) -> watch::Receiver { - settings::channel(&cx.font_cache()).unwrap().1 -} - -pub fn build_app_state(cx: &mut MutableAppContext) -> Arc { - let (settings_tx, settings) = settings::channel(&cx.font_cache()).unwrap(); +pub fn test_app_state(cx: &mut MutableAppContext) -> Arc { + let (settings_tx, settings) = settings::test(cx); let languages = Arc::new(LanguageRegistry::new()); - let themes = ThemeRegistry::new(()); + let themes = ThemeRegistry::new((), cx.font_cache().clone()); let rpc = rpc::Client::new(); let user_store = Arc::new(UserStore::new(rpc.clone())); Arc::new(AppState { diff --git a/zed/src/theme.rs b/zed/src/theme.rs index 3a8430c4be..0346eba0e8 100644 --- a/zed/src/theme.rs +++ b/zed/src/theme.rs @@ -5,9 +5,9 @@ use anyhow::Result; use gpui::{ color::Color, elements::{ContainerStyle, LabelStyle}, - fonts::TextStyle, + fonts::{HighlightStyle, TextStyle}, }; -use serde::{Deserialize, Deserializer}; +use serde::{de, Deserialize}; use std::collections::HashMap; pub use highlight_map::*; @@ -15,7 +15,7 @@ pub use theme_registry::*; pub const DEFAULT_THEME_NAME: &'static str = "dark"; -#[derive(Debug, Default, Deserialize)] +#[derive(Deserialize)] pub struct Theme { #[serde(default)] pub name: String, @@ -23,11 +23,15 @@ pub struct Theme { pub chat_panel: ChatPanel, pub selector: Selector, pub editor: Editor, - #[serde(deserialize_with = "deserialize_syntax_theme")] - pub syntax: Vec<(String, TextStyle)>, + pub syntax: SyntaxTheme, } -#[derive(Debug, Default, Deserialize)] +pub struct SyntaxTheme { + highlights: Vec<(String, HighlightStyle)>, + default_style: HighlightStyle, +} + +#[derive(Deserialize)] pub struct Workspace { pub background: Color, pub tab: Tab, @@ -37,7 +41,7 @@ pub struct Workspace { pub active_sidebar_icon: SidebarIcon, } -#[derive(Debug, Default, Deserialize)] +#[derive(Deserialize)] pub struct Tab { #[serde(flatten)] pub container: ContainerStyle, @@ -48,26 +52,26 @@ pub struct Tab { pub icon_conflict: Color, } -#[derive(Debug, Default, Deserialize)] +#[derive(Deserialize)] pub struct SidebarIcon { pub color: Color, } -#[derive(Debug, Default, Deserialize)] +#[derive(Deserialize)] pub struct ChatPanel { #[serde(flatten)] pub container: ContainerStyle, pub message: ChatMessage, } -#[derive(Debug, Default, Deserialize)] +#[derive(Deserialize)] pub struct ChatMessage { pub body: TextStyle, pub sender: ContainedLabel, pub timestamp: ContainedLabel, } -#[derive(Debug, Default, Deserialize)] +#[derive(Deserialize)] pub struct Selector { #[serde(flatten)] pub container: ContainerStyle, @@ -78,7 +82,7 @@ pub struct Selector { pub active_item: ContainedLabel, } -#[derive(Debug, Default, Deserialize)] +#[derive(Deserialize)] pub struct ContainedLabel { #[serde(flatten)] pub container: ContainerStyle, @@ -86,70 +90,69 @@ pub struct ContainedLabel { pub label: LabelStyle, } -#[derive(Debug, Deserialize)] +#[derive(Deserialize)] pub struct Editor { pub background: Color, pub gutter_background: Color, pub active_line_background: Color, pub line_number: Color, pub line_number_active: Color, - pub text: Color, pub replicas: Vec, } -#[derive(Clone, Copy, Debug, Default, Deserialize)] +#[derive(Clone, Copy, Deserialize)] pub struct Replica { pub cursor: Color, pub selection: Color, } -impl Theme { - pub fn highlight_style(&self, id: HighlightId) -> TextStyle { - self.syntax +impl SyntaxTheme { + pub fn new(default_style: HighlightStyle, highlights: Vec<(String, HighlightStyle)>) -> Self { + Self { + default_style, + highlights, + } + } + + pub fn highlight_style(&self, id: HighlightId) -> HighlightStyle { + self.highlights .get(id.0 as usize) .map(|entry| entry.1.clone()) - .unwrap_or_else(|| TextStyle { - color: self.editor.text, - font_properties: Default::default(), - }) + .unwrap_or_else(|| self.default_style.clone()) } #[cfg(test)] pub fn highlight_name(&self, id: HighlightId) -> Option<&str> { - self.syntax.get(id.0 as usize).map(|e| e.0.as_str()) + self.highlights.get(id.0 as usize).map(|e| e.0.as_str()) } } -impl Default for Editor { - fn default() -> Self { - Self { - background: Default::default(), - gutter_background: Default::default(), - active_line_background: Default::default(), - line_number: Default::default(), - line_number_active: Default::default(), - text: Default::default(), - replicas: vec![Replica::default()], - } - } -} +impl<'de> Deserialize<'de> for SyntaxTheme { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let mut syntax_data: HashMap = + Deserialize::deserialize(deserializer)?; -pub fn deserialize_syntax_theme<'de, D>( - deserializer: D, -) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let mut result = Vec::<(String, TextStyle)>::new(); + let mut result = Self { + highlights: Vec::<(String, HighlightStyle)>::new(), + default_style: syntax_data + .remove("default") + .ok_or_else(|| de::Error::custom("must specify a default color in syntax theme"))?, + }; - let syntax_data: HashMap = Deserialize::deserialize(deserializer)?; - for (key, style) in syntax_data { - match result.binary_search_by(|(needle, _)| needle.cmp(&key)) { - Ok(i) | Err(i) => { - result.insert(i, (key, style)); + for (key, style) in syntax_data { + match result + .highlights + .binary_search_by(|(needle, _)| needle.cmp(&key)) + { + Ok(i) | Err(i) => { + result.highlights.insert(i, (key, style)); + } } } - } - Ok(result) + Ok(result) + } } diff --git a/zed/src/theme/highlight_map.rs b/zed/src/theme/highlight_map.rs index 55a053113c..c030e2ab1a 100644 --- a/zed/src/theme/highlight_map.rs +++ b/zed/src/theme/highlight_map.rs @@ -1,4 +1,4 @@ -use super::Theme; +use super::SyntaxTheme; use std::sync::Arc; #[derive(Clone, Debug)] @@ -10,7 +10,7 @@ pub struct HighlightId(pub u32); const DEFAULT_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX); impl HighlightMap { - pub fn new(capture_names: &[String], theme: &Theme) -> Self { + pub fn new(capture_names: &[String], theme: &SyntaxTheme) -> Self { // For each capture name in the highlight query, find the longest // key in the theme's syntax styles that matches all of the // dot-separated components of the capture name. @@ -19,7 +19,7 @@ impl HighlightMap { .iter() .map(|capture_name| { theme - .syntax + .highlights .iter() .enumerate() .filter_map(|(i, (key, _))| { @@ -68,9 +68,9 @@ mod tests { #[test] fn test_highlight_map() { - let theme = Theme { - name: "test".into(), - syntax: [ + let theme = SyntaxTheme::new( + Default::default(), + [ ("function", Color::from_u32(0x100000ff)), ("function.method", Color::from_u32(0x200000ff)), ("function.async", Color::from_u32(0x300000ff)), @@ -81,8 +81,7 @@ mod tests { .iter() .map(|(name, color)| (name.to_string(), (*color).into())) .collect(), - ..Default::default() - }; + ); let capture_names = &[ "function.special".to_string(), diff --git a/zed/src/theme/theme_registry.rs b/zed/src/theme/theme_registry.rs index 0b68d8a0c9..da6d097c31 100644 --- a/zed/src/theme/theme_registry.rs +++ b/zed/src/theme/theme_registry.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Context, Result}; -use gpui::AssetSource; +use gpui::{fonts, AssetSource, FontCache}; use json::{Map, Value}; use parking_lot::Mutex; use serde_json as json; @@ -11,6 +11,7 @@ pub struct ThemeRegistry { assets: Box, themes: Mutex>>, theme_data: Mutex>>, + font_cache: Arc, } #[derive(Default)] @@ -38,11 +39,12 @@ enum Key { } impl ThemeRegistry { - pub fn new(source: impl AssetSource) -> Arc { + pub fn new(source: impl AssetSource, font_cache: Arc) -> Arc { Arc::new(Self { assets: Box::new(source), themes: Default::default(), theme_data: Default::default(), + font_cache, }) } @@ -69,7 +71,10 @@ impl ThemeRegistry { } let theme_data = self.load(name, true)?; - let mut theme = serde_json::from_value::(theme_data.as_ref().clone())?; + let mut theme = fonts::with_font_cache(self.font_cache.clone(), || { + serde_json::from_value::(theme_data.as_ref().clone()) + })?; + theme.name = name.into(); let theme = Arc::new(theme); self.themes.lock().insert(name.to_string(), theme.clone()); @@ -512,11 +517,12 @@ fn value_at<'a>(object: &'a mut Map, key_path: &KeyPath) -> Optio mod tests { use super::*; use crate::{assets::Assets, theme::DEFAULT_THEME_NAME}; + use gpui::MutableAppContext; use rand::{prelude::StdRng, Rng}; - #[test] - fn test_bundled_themes() { - let registry = ThemeRegistry::new(Assets); + #[gpui::test] + fn test_bundled_themes(cx: &mut MutableAppContext) { + let registry = ThemeRegistry::new(Assets, cx.font_cache().clone()); let mut has_default_theme = false; for theme_name in registry.list() { let theme = registry.get(&theme_name).unwrap(); @@ -528,8 +534,8 @@ mod tests { assert!(has_default_theme); } - #[test] - fn test_theme_extension() { + #[gpui::test] + fn test_theme_extension(cx: &mut MutableAppContext) { let assets = TestAssets(&[ ( "themes/_base.toml", @@ -568,7 +574,7 @@ mod tests { ), ]); - let registry = ThemeRegistry::new(assets); + let registry = ThemeRegistry::new(assets, cx.font_cache().clone()); let theme_data = registry.load("light", true).unwrap(); assert_eq!( theme_data.as_ref(), diff --git a/zed/src/theme_selector.rs b/zed/src/theme_selector.rs index 867c5b7693..2b0eea969e 100644 --- a/zed/src/theme_selector.rs +++ b/zed/src/theme_selector.rs @@ -204,13 +204,7 @@ impl ThemeSelector { if self.matches.is_empty() { let settings = self.settings.borrow(); return Container::new( - Label::new( - "No matches".into(), - settings.ui_font_family, - settings.ui_font_size, - ) - .with_style(&settings.theme.selector.label) - .boxed(), + Label::new("No matches".into(), settings.theme.selector.label.clone()).boxed(), ) .with_margin_top(6.0) .named("empty matches"); @@ -247,14 +241,12 @@ impl ThemeSelector { let container = Container::new( Label::new( theme_match.string.clone(), - settings.ui_font_family, - settings.ui_font_size, + if index == self.selected_index { + theme.selector.active_item.label.clone() + } else { + theme.selector.item.label.clone() + }, ) - .with_style(if index == self.selected_index { - &theme.selector.active_item.label - } else { - &theme.selector.item.label - }) .with_highlights(theme_match.positions.clone()) .boxed(), ) diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index e037460891..e5785028a3 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -1018,7 +1018,7 @@ mod tests { use crate::{ editor::{Editor, Insert}, fs::FakeFs, - test::{build_app_state, temp_tree}, + test::{temp_tree, test_app_state}, worktree::WorktreeHandle, }; use serde_json::json; @@ -1027,7 +1027,7 @@ mod tests { #[gpui::test] async fn test_open_paths_action(mut cx: gpui::TestAppContext) { - let app_state = cx.update(build_app_state); + let app_state = cx.update(test_app_state); let dir = temp_tree(json!({ "a": { "aa": null, @@ -1100,7 +1100,7 @@ mod tests { }, })); - let app_state = cx.update(build_app_state); + let app_state = cx.update(test_app_state); let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); workspace @@ -1204,7 +1204,7 @@ mod tests { fs.insert_file("/dir1/a.txt", "".into()).await.unwrap(); fs.insert_file("/dir2/b.txt", "".into()).await.unwrap(); - let mut app_state = cx.update(build_app_state); + let mut app_state = cx.update(test_app_state); Arc::get_mut(&mut app_state).unwrap().fs = Arc::new(fs); let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); @@ -1273,7 +1273,7 @@ mod tests { "a.txt": "", })); - let app_state = cx.update(build_app_state); + let app_state = cx.update(test_app_state); let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); workspace .update(&mut cx, |workspace, cx| { @@ -1318,7 +1318,7 @@ mod tests { #[gpui::test] async fn test_open_and_save_new_file(mut cx: gpui::TestAppContext) { let dir = TempDir::new("test-new-file").unwrap(); - let app_state = cx.update(build_app_state); + let app_state = cx.update(test_app_state); let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); workspace .update(&mut cx, |workspace, cx| { @@ -1417,7 +1417,7 @@ mod tests { async fn test_new_empty_workspace(mut cx: gpui::TestAppContext) { cx.update(init); - let app_state = cx.update(build_app_state); + let app_state = cx.update(test_app_state); cx.dispatch_global_action(OpenNew(app_state)); let window_id = *cx.window_ids().first().unwrap(); let workspace = cx.root_view::(window_id).unwrap(); @@ -1463,7 +1463,7 @@ mod tests { }, })); - let app_state = cx.update(build_app_state); + let app_state = cx.update(test_app_state); let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&app_state, cx)); workspace .update(&mut cx, |workspace, cx| { diff --git a/zed/src/workspace/pane.rs b/zed/src/workspace/pane.rs index ab74cac191..1be0fccd58 100644 --- a/zed/src/workspace/pane.rs +++ b/zed/src/workspace/pane.rs @@ -208,14 +208,12 @@ impl Pane { Align::new( Label::new( title, - settings.ui_font_family, - settings.ui_font_size, + if is_active { + theme.workspace.active_tab.label.clone() + } else { + theme.workspace.tab.label.clone() + }, ) - .with_style(if is_active { - &theme.workspace.active_tab.label - } else { - &theme.workspace.tab.label - }) .boxed(), ) .boxed(),