diff --git a/gpui/src/color.rs b/gpui/src/color.rs index 818b07111d..9c6de6247a 100644 --- a/gpui/src/color.rs +++ b/gpui/src/color.rs @@ -1,11 +1,15 @@ use std::{ + borrow::Cow, fmt, ops::{Deref, DerefMut}, }; use crate::json::ToJson; use pathfinder_color::ColorU; -use serde::{Deserialize, Deserializer}; +use serde::{ + de::{self, Unexpected}, + Deserialize, Deserializer, +}; use serde_json::json; #[derive(Clone, Copy, Default, PartialEq, Eq, Hash)] @@ -39,13 +43,20 @@ impl<'de> Deserialize<'de> for Color { where D: Deserializer<'de>, { - let mut rgba = u32::deserialize(deserializer)?; - - if rgba <= 0xFFFFFF { - rgba = (rgba << 8) + 0xFF; + let literal: Cow = Deserialize::deserialize(deserializer)?; + if let Some(digits) = literal.strip_prefix('#') { + if let Ok(value) = u32::from_str_radix(digits, 16) { + if digits.len() == 6 { + return Ok(Color::from_u32((value << 8) | 0xFF)); + } else if digits.len() == 8 { + return Ok(Color::from_u32(value)); + } + } } - - Ok(Self::from_u32(rgba)) + Err(de::Error::invalid_value( + Unexpected::Str(literal.as_ref()), + &"#RRGGBB[AA]", + )) } } diff --git a/gpui/src/elements/label.rs b/gpui/src/elements/label.rs index ba9b9e8c5e..72f755905c 100644 --- a/gpui/src/elements/label.rs +++ b/gpui/src/elements/label.rs @@ -1,7 +1,7 @@ use crate::{ color::Color, font_cache::FamilyId, - fonts::{deserialize_font_properties, deserialize_option_font_properties, FontId, Properties}, + fonts::{FontId, TextStyle}, geometry::{ rect::RectF, vector::{vec2f, Vector2F}, @@ -25,14 +25,8 @@ pub struct Label { #[derive(Clone, Debug, Default, Deserialize)] pub struct LabelStyle { - #[serde(default = "Color::black")] - pub color: Color, - #[serde(default)] - pub highlight_color: Option, - #[serde(default, deserialize_with = "deserialize_font_properties")] - pub font_properties: Properties, - #[serde(default, deserialize_with = "deserialize_option_font_properties")] - pub highlight_font_properties: Option, + pub text: TextStyle, + pub highlight_text: Option, } impl Label { @@ -52,7 +46,7 @@ impl Label { } pub fn with_default_color(mut self, color: Color) -> Self { - self.style.color = color; + self.style.text.color = color; self } @@ -67,13 +61,18 @@ impl Label { font_id: FontId, ) -> SmallVec<[(usize, FontId, Color); 8]> { if self.highlight_indices.is_empty() { - return smallvec![(self.text.len(), font_id, self.style.color)]; + return smallvec![(self.text.len(), font_id, self.style.text.color)]; } let highlight_font_id = self .style - .highlight_font_properties - .and_then(|properties| font_cache.select_font(self.family_id, &properties).ok()) + .highlight_text + .as_ref() + .and_then(|style| { + font_cache + .select_font(self.family_id, &style.font_properties) + .ok() + }) .unwrap_or(font_id); let mut highlight_indices = self.highlight_indices.iter().copied().peekable(); @@ -81,11 +80,16 @@ impl Label { for (char_ix, c) in self.text.char_indices() { let mut font_id = font_id; - let mut color = self.style.color; + let mut color = self.style.text.color; if let Some(highlight_ix) = highlight_indices.peek() { if char_ix == *highlight_ix { font_id = highlight_font_id; - color = self.style.highlight_color.unwrap_or(self.style.color); + color = self + .style + .highlight_text + .as_ref() + .unwrap_or(&self.style.text) + .color; highlight_indices.next(); } } @@ -121,7 +125,7 @@ impl Element for Label { ) -> (Vector2F, Self::LayoutState) { let font_id = cx .font_cache - .select_font(self.family_id, &self.style.font_properties) + .select_font(self.family_id, &self.style.text.font_properties) .unwrap(); let runs = self.compute_runs(&cx.font_cache, font_id); let line = @@ -185,40 +189,43 @@ impl Element for Label { impl ToJson for LabelStyle { fn to_json(&self) -> Value { json!({ - "default_color": self.color.to_json(), - "default_font_properties": self.font_properties.to_json(), - "highlight_color": self.highlight_color.to_json(), - "highlight_font_properties": self.highlight_font_properties.to_json(), + "text": self.text.to_json(), + "highlight_text": self.highlight_text + .as_ref() + .map_or(serde_json::Value::Null, |style| style.to_json()) }) } } #[cfg(test)] mod tests { - use font_kit::properties::Weight; - use super::*; + use crate::fonts::{Properties as FontProperties, Weight}; #[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, &Properties::new()) + .select_font(menlo, &FontProperties::new()) .unwrap(); let menlo_bold = cx .font_cache() - .select_font(menlo, Properties::new().weight(Weight::BOLD)) + .select_font(menlo, FontProperties::new().weight(Weight::BOLD)) .unwrap(); let black = Color::black(); let red = Color::new(255, 0, 0, 255); let label = Label::new(".αβγδε.ⓐⓑⓒⓓⓔ.abcde.".to_string(), menlo, 12.0) .with_style(&LabelStyle { - color: black, - highlight_color: Some(red), - highlight_font_properties: Some(*Properties::new().weight(Weight::BOLD)), - ..Default::default() + 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(), diff --git a/gpui/src/fonts.rs b/gpui/src/fonts.rs index 4a9bf0a9df..e9f84676e7 100644 --- a/gpui/src/fonts.rs +++ b/gpui/src/fonts.rs @@ -1,15 +1,25 @@ -use crate::json::{json, ToJson}; +use crate::{ + color::Color, + json::{json, ToJson}, +}; pub use font_kit::{ metrics::Metrics, properties::{Properties, Stretch, Style, Weight}, }; -use serde::{Deserialize, Deserializer}; +use serde::{de, Deserialize}; +use serde_json::Value; #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct FontId(pub usize); pub type GlyphId = u32; +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct TextStyle { + pub color: Color, + pub font_properties: Properties, +} + #[allow(non_camel_case_types)] #[derive(Deserialize)] enum WeightJson { @@ -25,16 +35,53 @@ enum WeightJson { } #[derive(Deserialize)] -struct PropertiesJson { +struct TextStyleJson { + color: Color, weight: Option, #[serde(default)] italic: bool, } -impl Into for PropertiesJson { - fn into(self) -> Properties { - let mut result = Properties::new(); - result.weight = match self.weight.unwrap_or(WeightJson::normal) { +impl<'de> Deserialize<'de> for TextStyle { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let 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()) + } else { + Ok(Self { + color: serde_json::from_value(json).map_err(de::Error::custom)?, + font_properties: Properties::new(), + }) + } + } +} + +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, @@ -45,37 +92,18 @@ impl Into for PropertiesJson { WeightJson::extra_bold => Weight::EXTRA_BOLD, WeightJson::black => Weight::BLACK, }; - if self.italic { - result.style = Style::Italic; + let style = if self.italic { + Style::Italic + } else { + Style::Normal + }; + TextStyle { + color: self.color, + font_properties: *Properties::new().weight(weight).style(style), } - result } } -pub fn deserialize_option_font_properties<'de, D>( - deserializer: D, -) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let json: Option = Deserialize::deserialize(deserializer)?; - Ok(json.map(Into::into)) -} - -pub fn deserialize_font_properties<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let json: PropertiesJson = Deserialize::deserialize(deserializer)?; - Ok(json.into()) -} - -pub fn font_properties_from_json( - value: serde_json::Value, -) -> Result { - Ok(serde_json::from_value::(value)?.into()) -} - impl ToJson for Properties { fn to_json(&self) -> crate::json::Value { json!({ diff --git a/zed/assets/themes/_base.toml b/zed/assets/themes/_base.toml index fb73a3830d..1b646b40a2 100644 --- a/zed/assets/themes/_base.toml +++ b/zed/assets/themes/_base.toml @@ -3,34 +3,35 @@ background = "$elevation_1" [ui.tab] background = "$elevation_2" -color = "$text_dull" -border.color = 0x000000 -icon_close = 0x383839 -icon_dirty = 0x556de8 -icon_conflict = 0xe45349 +text = "$text_dull" +border.color = "#000000" +icon_close = "#383839" +icon_dirty = "#556de8" +icon_conflict = "#e45349" [ui.active_tab] -extends = ".." +extends = "ui.tab" background = "$elevation_3" -color = "$text_bright" +text = "$text_bright" [ui.selector] background = "$elevation_4" +text = "$text_bright" padding = { top = 6.0, bottom = 6.0, left = 6.0, right = 6.0 } margin.top = 12.0 corner_radius = 6.0 -shadow = { offset = [0.0, 0.0], blur = 12.0, color = 0x00000088 } +shadow = { offset = [0.0, 0.0], blur = 12.0, color = "#00000088" } [ui.selector.item] -background = 0x424344 -text = 0xcccccc -highlight_text = 0x18a3ff -highlight_font_properties = { weight = "bold" } -border = { color = 0x000000, width = 1.0 } +background = "#424344" +text = "#cccccc" +highlight_text = { color = "#18a3ff", weight = "bold" } +border = { color = "#000000", width = 1.0 } +padding = { top = 6.0, bottom = 6.0, left = 6.0, right = 6.0 } [ui.selector.active_item] -extends = ".." -background = 0x094771 +extends = "ui.selector.item" +background = "#094771" [editor] background = "$elevation_3" @@ -40,6 +41,6 @@ line_number = "$text_dull" line_number_active = "$text_bright" text = "$text_normal" replicas = [ - { selection = 0x264f78, cursor = "$text_bright" }, - { selection = 0x504f31, cursor = 0xfcf154 }, + { selection = "#264f78", cursor = "$text_bright" }, + { selection = "#504f31", cursor = "#fcf154" }, ] diff --git a/zed/assets/themes/dark.toml b/zed/assets/themes/dark.toml index fdf5a5adee..5db98bf091 100644 --- a/zed/assets/themes/dark.toml +++ b/zed/assets/themes/dark.toml @@ -1,21 +1,21 @@ extends = "_base" [variables] -elevation_1 = 0x050101 -elevation_2 = 0x131415 -elevation_3 = 0x1c1d1e -elevation_4 = 0x3a3b3c -text_dull = 0x5a5a5b -text_bright = 0xffffff -text_normal = 0xd4d4d4 +elevation_1 = "#050101" +elevation_2 = "#131415" +elevation_3 = "#1c1d1e" +elevation_4 = "#3a3b3c" +text_dull = "#5a5a5b" +text_bright = "#ffffff" +text_normal = "#d4d4d4" [syntax] -keyword = 0xc586c0 -function = 0xdcdcaa -string = 0xcb8f77 -type = 0x4ec9b0 -number = 0xb5cea8 -comment = 0x6a9955 -property = 0x4e94ce -variant = 0x4fc1ff -constant = 0x9cdcfe +keyword = { color = "#c586c0", weight = "bold" } +function = "#dcdcaa" +string = "#cb8f77" +type = "#4ec9b0" +number = "#b5cea8" +comment = "#6a9955" +property = "#4e94ce" +variant = "#4fc1ff" +constant = "#9cdcfe" diff --git a/zed/src/editor.rs b/zed/src/editor.rs index 7283e8f6b4..d5507f38c9 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -4,7 +4,7 @@ mod element; pub mod movement; use crate::{ - settings::{Settings, StyleId, Theme}, + settings::{HighlightId, Settings, Theme}, time::ReplicaId, util::{post_inc, Bias}, workspace, @@ -2419,7 +2419,7 @@ impl Snapshot { .display_snapshot .highlighted_chunks_for_rows(rows.clone()); - 'outer: for (chunk, style_ix) in chunks.chain(Some(("\n", StyleId::default()))) { + 'outer: for (chunk, style_ix) in chunks.chain(Some(("\n", HighlightId::default()))) { for (ix, mut line_chunk) in chunk.split('\n').enumerate() { if ix > 0 { layouts.push(layout_cache.layout_str(&line, self.font_size, &styles)); @@ -2433,12 +2433,12 @@ impl Snapshot { } if !line_chunk.is_empty() && !line_exceeded_max_len { - let (color, font_properties) = self.theme.syntax_style(style_ix); + let style = self.theme.highlight_style(style_ix); // Avoid a lookup if the font properties match the previous ones. - let font_id = if font_properties == prev_font_properties { + let font_id = if style.font_properties == prev_font_properties { prev_font_id } else { - font_cache.select_font(self.font_family, &font_properties)? + font_cache.select_font(self.font_family, &style.font_properties)? }; if line.len() + line_chunk.len() > MAX_LINE_LEN { @@ -2451,9 +2451,9 @@ impl Snapshot { } line.push_str(line_chunk); - styles.push((line_chunk.len(), font_id, color)); + styles.push((line_chunk.len(), font_id, style.color)); prev_font_id = font_id; - prev_font_properties = font_properties; + prev_font_properties = style.font_properties; } } } diff --git a/zed/src/editor/buffer.rs b/zed/src/editor/buffer.rs index 3a3876d808..9b1f169e48 100644 --- a/zed/src/editor/buffer.rs +++ b/zed/src/editor/buffer.rs @@ -16,7 +16,7 @@ use zrpc::proto; use crate::{ language::{Language, Tree}, operation_queue::{self, OperationQueue}, - settings::{StyleId, ThemeMap}, + settings::{HighlightId, HighlightMap}, sum_tree::{self, FilterCursor, SumTree}, time::{self, ReplicaId}, util::Bias, @@ -1985,7 +1985,7 @@ impl Snapshot { captures, next_capture: None, stack: Default::default(), - theme_mapping: language.theme_mapping(), + highlight_map: language.highlight_map(), }), } } else { @@ -2316,8 +2316,8 @@ impl<'a> tree_sitter::TextProvider<'a> for TextProvider<'a> { struct Highlights<'a> { captures: tree_sitter::QueryCaptures<'a, 'a, TextProvider<'a>>, next_capture: Option<(tree_sitter::QueryMatch<'a, 'a>, usize)>, - stack: Vec<(usize, StyleId)>, - theme_mapping: ThemeMap, + stack: Vec<(usize, HighlightId)>, + highlight_map: HighlightMap, } pub struct HighlightedChunks<'a> { @@ -2341,7 +2341,7 @@ impl<'a> HighlightedChunks<'a> { if offset < next_capture_end { highlights.stack.push(( next_capture_end, - highlights.theme_mapping.get(capture.index), + highlights.highlight_map.get(capture.index), )); } highlights.next_capture.take(); @@ -2357,7 +2357,7 @@ impl<'a> HighlightedChunks<'a> { } impl<'a> Iterator for HighlightedChunks<'a> { - type Item = (&'a str, StyleId); + type Item = (&'a str, HighlightId); fn next(&mut self) -> Option { let mut next_capture_start = usize::MAX; @@ -2381,7 +2381,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { next_capture_start = capture.node.start_byte(); break; } else { - let style_id = highlights.theme_mapping.get(capture.index); + let style_id = highlights.highlight_map.get(capture.index); highlights.stack.push((capture.node.end_byte(), style_id)); highlights.next_capture = highlights.captures.next(); } @@ -2391,7 +2391,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { if let Some(chunk) = self.chunks.peek() { let chunk_start = self.range.start; let mut chunk_end = (self.chunks.offset() + chunk.len()).min(next_capture_start); - let mut style_id = StyleId::default(); + let mut style_id = HighlightId::default(); if let Some((parent_capture_end, parent_style_id)) = self.highlights.as_ref().and_then(|h| h.stack.last()) { diff --git a/zed/src/editor/display_map.rs b/zed/src/editor/display_map.rs index 67246bd983..ab97179a6b 100644 --- a/zed/src/editor/display_map.rs +++ b/zed/src/editor/display_map.rs @@ -654,16 +654,8 @@ mod tests { .unwrap(); let theme = Theme { syntax: vec![ - ( - "mod.body".to_string(), - Color::from_u32(0xff0000ff), - Default::default(), - ), - ( - "fn.name".to_string(), - Color::from_u32(0x00ff00ff), - Default::default(), - ), + ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()), + ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()), ], ..Default::default() }; @@ -676,7 +668,7 @@ mod tests { grammar: grammar.clone(), highlight_query, brackets_query: tree_sitter::Query::new(grammar, "").unwrap(), - theme_mapping: Default::default(), + highlight_map: Default::default(), }); lang.set_theme(&theme); @@ -752,16 +744,8 @@ mod tests { .unwrap(); let theme = Theme { syntax: vec![ - ( - "mod.body".to_string(), - Color::from_u32(0xff0000ff), - Default::default(), - ), - ( - "fn.name".to_string(), - Color::from_u32(0x00ff00ff), - Default::default(), - ), + ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()), + ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()), ], ..Default::default() }; @@ -774,7 +758,7 @@ mod tests { grammar: grammar.clone(), highlight_query, brackets_query: tree_sitter::Query::new(grammar, "").unwrap(), - theme_mapping: Default::default(), + highlight_map: Default::default(), }); lang.set_theme(&theme); @@ -953,7 +937,7 @@ mod tests { let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx)); let mut chunks: Vec<(String, Option<&str>)> = Vec::new(); for (chunk, style_id) in snapshot.highlighted_chunks_for_rows(rows) { - let style_name = theme.syntax_style_name(style_id); + let style_name = theme.highlight_name(style_id); if let Some((last_chunk, last_style_name)) = chunks.last_mut() { if style_name == *last_style_name { last_chunk.push_str(chunk); diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index b8ab39cce1..3bfa1f3d24 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -4,7 +4,7 @@ use super::{ }; use crate::{ editor::buffer, - settings::StyleId, + settings::HighlightId, sum_tree::{self, Cursor, FilterCursor, SumTree}, time, util::Bias, @@ -1004,12 +1004,12 @@ impl<'a> Iterator for Chunks<'a> { pub struct HighlightedChunks<'a> { transform_cursor: Cursor<'a, Transform, FoldOffset, usize>, buffer_chunks: buffer::HighlightedChunks<'a>, - buffer_chunk: Option<(usize, &'a str, StyleId)>, + buffer_chunk: Option<(usize, &'a str, HighlightId)>, buffer_offset: usize, } impl<'a> Iterator for HighlightedChunks<'a> { - type Item = (&'a str, StyleId); + type Item = (&'a str, HighlightId); fn next(&mut self) -> Option { let transform = if let Some(item) = self.transform_cursor.item() { @@ -1031,7 +1031,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { self.transform_cursor.next(&()); } - return Some((output_text, StyleId::default())); + return Some((output_text, HighlightId::default())); } // Retrieve a chunk from the current location in the buffer. diff --git a/zed/src/editor/display_map/tab_map.rs b/zed/src/editor/display_map/tab_map.rs index 99257c847f..299320ca32 100644 --- a/zed/src/editor/display_map/tab_map.rs +++ b/zed/src/editor/display_map/tab_map.rs @@ -1,7 +1,7 @@ use parking_lot::Mutex; use super::fold_map::{self, FoldEdit, FoldPoint, Snapshot as FoldSnapshot}; -use crate::{editor::rope, settings::StyleId, util::Bias}; +use crate::{editor::rope, settings::HighlightId, util::Bias}; use std::{mem, ops::Range}; pub struct TabMap(Mutex); @@ -416,14 +416,14 @@ impl<'a> Iterator for Chunks<'a> { pub struct HighlightedChunks<'a> { fold_chunks: fold_map::HighlightedChunks<'a>, chunk: &'a str, - style_id: StyleId, + style_id: HighlightId, column: usize, tab_size: usize, skip_leading_tab: bool, } impl<'a> Iterator for HighlightedChunks<'a> { - type Item = (&'a str, StyleId); + type Item = (&'a str, HighlightId); fn next(&mut self) -> Option { if self.chunk.is_empty() { diff --git a/zed/src/editor/display_map/wrap_map.rs b/zed/src/editor/display_map/wrap_map.rs index 18d6dbba6a..358730748b 100644 --- a/zed/src/editor/display_map/wrap_map.rs +++ b/zed/src/editor/display_map/wrap_map.rs @@ -5,7 +5,7 @@ use super::{ }; use crate::{ editor::Point, - settings::StyleId, + settings::HighlightId, sum_tree::{self, Cursor, SumTree}, util::Bias, Settings, @@ -59,7 +59,7 @@ pub struct Chunks<'a> { pub struct HighlightedChunks<'a> { input_chunks: tab_map::HighlightedChunks<'a>, input_chunk: &'a str, - style_id: StyleId, + style_id: HighlightId, output_position: WrapPoint, max_output_row: u32, transforms: Cursor<'a, Transform, WrapPoint, TabPoint>, @@ -487,7 +487,7 @@ impl Snapshot { HighlightedChunks { input_chunks: self.tab_snapshot.highlighted_chunks(input_start..input_end), input_chunk: "", - style_id: StyleId::default(), + style_id: HighlightId::default(), output_position: output_start, max_output_row: rows.end, transforms, @@ -670,7 +670,7 @@ impl<'a> Iterator for Chunks<'a> { } impl<'a> Iterator for HighlightedChunks<'a> { - type Item = (&'a str, StyleId); + type Item = (&'a str, HighlightId); fn next(&mut self) -> Option { if self.output_position.row() >= self.max_output_row { diff --git a/zed/src/file_finder.rs b/zed/src/file_finder.rs index 3bd1e54c82..8e2e0bfa57 100644 --- a/zed/src/file_finder.rs +++ b/zed/src/file_finder.rs @@ -141,11 +141,15 @@ impl FileFinder { index: usize, cx: &AppContext, ) -> Option { + let selected_index = self.selected_index(); let settings = self.settings.borrow(); - let theme = &settings.theme.ui; + let style = if index == selected_index { + &settings.theme.ui.selector.active_item + } else { + &settings.theme.ui.selector.item + }; self.labels_for_match(path_match, cx).map( |(file_name, file_name_positions, full_path, full_path_positions)| { - let selected_index = self.selected_index(); let container = Container::new( Flex::row() .with_child( @@ -170,7 +174,7 @@ impl FileFinder { settings.ui_font_family, settings.ui_font_size, ) - .with_style(&theme.selector.label) + .with_style(&style.label) .with_highlights(file_name_positions) .boxed(), ) @@ -180,7 +184,7 @@ impl FileFinder { settings.ui_font_family, settings.ui_font_size, ) - .with_style(&theme.selector.label) + .with_style(&style.label) .with_highlights(full_path_positions) .boxed(), ) @@ -190,12 +194,7 @@ impl FileFinder { ) .boxed(), ) - .with_uniform_padding(6.0) - .with_style(if index == selected_index { - &theme.selector.active_item.container - } else { - &theme.selector.item.container - }); + .with_style(&style.container); let entry = (path_match.tree_id, path_match.path.clone()); EventHandler::new(container.boxed()) diff --git a/zed/src/language.rs b/zed/src/language.rs index 7d286dfbe3..886befe595 100644 --- a/zed/src/language.rs +++ b/zed/src/language.rs @@ -1,4 +1,4 @@ -use crate::settings::{Theme, ThemeMap}; +use crate::settings::{HighlightMap, Theme}; use parking_lot::Mutex; use rust_embed::RustEmbed; use serde::Deserialize; @@ -27,7 +27,7 @@ pub struct Language { pub grammar: Grammar, pub highlight_query: Query, pub brackets_query: Query, - pub theme_mapping: Mutex, + pub highlight_map: Mutex, } pub struct LanguageRegistry { @@ -35,12 +35,12 @@ pub struct LanguageRegistry { } impl Language { - pub fn theme_mapping(&self) -> ThemeMap { - self.theme_mapping.lock().clone() + pub fn highlight_map(&self) -> HighlightMap { + self.highlight_map.lock().clone() } pub fn set_theme(&self, theme: &Theme) { - *self.theme_mapping.lock() = ThemeMap::new(self.highlight_query.capture_names(), theme); + *self.highlight_map.lock() = HighlightMap::new(self.highlight_query.capture_names(), theme); } } @@ -53,7 +53,7 @@ impl LanguageRegistry { grammar, highlight_query: Self::load_query(grammar, "rust/highlights.scm"), brackets_query: Self::load_query(grammar, "rust/brackets.scm"), - theme_mapping: Mutex::new(ThemeMap::default()), + highlight_map: Mutex::new(HighlightMap::default()), }; Self { @@ -114,7 +114,7 @@ mod tests { grammar, highlight_query: Query::new(grammar, "").unwrap(), brackets_query: Query::new(grammar, "").unwrap(), - theme_mapping: Default::default(), + highlight_map: Default::default(), }), Arc::new(Language { config: LanguageConfig { @@ -125,7 +125,7 @@ mod tests { grammar, highlight_query: Query::new(grammar, "").unwrap(), brackets_query: Query::new(grammar, "").unwrap(), - theme_mapping: Default::default(), + highlight_map: Default::default(), }), ], }; diff --git a/zed/src/settings.rs b/zed/src/settings.rs index ad4bb80e00..81ae3c9e2a 100644 --- a/zed/src/settings.rs +++ b/zed/src/settings.rs @@ -4,7 +4,7 @@ use gpui::font_cache::{FamilyId, FontCache}; use postage::watch; use std::sync::Arc; -pub use theme::{StyleId, Theme, ThemeMap, ThemeRegistry}; +pub use theme::{HighlightId, HighlightMap, Theme, ThemeRegistry}; #[derive(Clone)] pub struct Settings { @@ -48,8 +48,13 @@ pub fn channel_with_themes( font_cache: &FontCache, themes: &ThemeRegistry, ) -> Result<(watch::Sender, watch::Receiver)> { + let theme = match themes.get("dark") { + Ok(theme) => dbg!(theme), + Err(err) => { + panic!("failed to deserialize default theme: {:?}", err) + } + }; Ok(watch::channel_with(Settings::new_with_theme( - font_cache, - themes.get("dark").expect("failed to load default theme"), + font_cache, theme, )?)) } diff --git a/zed/src/theme.rs b/zed/src/theme.rs index 0dcccc52f0..7334ef7862 100644 --- a/zed/src/theme.rs +++ b/zed/src/theme.rs @@ -2,16 +2,16 @@ use anyhow::{anyhow, Context, Result}; use gpui::{ color::Color, elements::{ContainerStyle, LabelStyle}, - fonts::{font_properties_from_json, Properties as FontProperties}, + fonts::TextStyle, AssetSource, }; use json::{Map, Value}; use parking_lot::Mutex; -use serde::{de, Deserialize, Deserializer}; +use serde::{Deserialize, Deserializer}; use serde_json as json; use std::{cmp::Ordering, collections::HashMap, sync::Arc}; -const DEFAULT_STYLE_ID: StyleId = StyleId(u32::MAX); +const DEFAULT_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX); pub struct ThemeRegistry { assets: Box, @@ -20,17 +20,17 @@ pub struct ThemeRegistry { } #[derive(Clone, Debug)] -pub struct ThemeMap(Arc<[StyleId]>); +pub struct HighlightMap(Arc<[HighlightId]>); #[derive(Clone, Copy, Debug)] -pub struct StyleId(u32); +pub struct HighlightId(u32); #[derive(Debug, Default, Deserialize)] pub struct Theme { pub ui: Ui, pub editor: Editor, #[serde(deserialize_with = "deserialize_syntax_theme")] - pub syntax: Vec<(String, Color, FontProperties)>, + pub syntax: Vec<(String, TextStyle)>, } #[derive(Debug, Default, Deserialize)] @@ -180,6 +180,7 @@ impl ThemeRegistry { } // If you extend something with an extend directive, process the source's extend directive first directives.sort_unstable(); + // Now update objects to include the fields of objects they extend for ExtendDirective { source_path, @@ -188,8 +189,11 @@ impl ThemeRegistry { { 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); + if let (Value::Object(mut source_object), Value::Object(target_object)) = + (source, target.take()) + { + deep_merge_json(&mut source_object, target_object); + *target = Value::Object(source_object); } } @@ -213,26 +217,28 @@ impl ThemeRegistry { } impl Theme { - pub fn syntax_style(&self, id: StyleId) -> (Color, FontProperties) { + pub fn highlight_style(&self, id: HighlightId) -> TextStyle { self.syntax .get(id.0 as usize) - .map_or((self.editor.text, FontProperties::new()), |entry| { - (entry.1, entry.2) + .map(|entry| entry.1.clone()) + .unwrap_or_else(|| TextStyle { + color: self.editor.text, + font_properties: Default::default(), }) } #[cfg(test)] - pub fn syntax_style_name(&self, id: StyleId) -> Option<&str> { + pub fn highlight_name(&self, id: HighlightId) -> Option<&str> { self.syntax.get(id.0 as usize).map(|e| e.0.as_str()) } } -impl ThemeMap { +impl HighlightMap { pub fn new(capture_names: &[String], theme: &Theme) -> 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. - ThemeMap( + HighlightMap( capture_names .iter() .map(|capture_name| { @@ -240,7 +246,7 @@ impl ThemeMap { .syntax .iter() .enumerate() - .filter_map(|(i, (key, _, _))| { + .filter_map(|(i, (key, _))| { let mut len = 0; let capture_parts = capture_name.split('.'); for key_part in key.split('.') { @@ -253,29 +259,29 @@ impl ThemeMap { Some((i, len)) }) .max_by_key(|(_, len)| *len) - .map_or(DEFAULT_STYLE_ID, |(i, _)| StyleId(i as u32)) + .map_or(DEFAULT_HIGHLIGHT_ID, |(i, _)| HighlightId(i as u32)) }) .collect(), ) } - pub fn get(&self, capture_id: u32) -> StyleId { + pub fn get(&self, capture_id: u32) -> HighlightId { self.0 .get(capture_id as usize) .copied() - .unwrap_or(DEFAULT_STYLE_ID) + .unwrap_or(DEFAULT_HIGHLIGHT_ID) } } -impl Default for ThemeMap { +impl Default for HighlightMap { fn default() -> Self { Self(Arc::new([])) } } -impl Default for StyleId { +impl Default for HighlightId { fn default() -> Self { - DEFAULT_STYLE_ID + DEFAULT_HIGHLIGHT_ID } } @@ -293,13 +299,13 @@ fn deep_merge_json(base: &mut Map, extension: Map) } } -#[derive(Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] enum Key { Array(usize), Object(String), } -#[derive(PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] struct ExtendDirective { source_path: Vec, target_path: Vec, @@ -429,30 +435,17 @@ fn validate_variable_name(name: &str) -> bool { pub fn deserialize_syntax_theme<'de, D>( deserializer: D, -) -> Result, D::Error> +) -> Result, D::Error> where D: Deserializer<'de>, { - let mut result = Vec::<(String, Color, FontProperties)>::new(); + let mut result = Vec::<(String, TextStyle)>::new(); - let syntax_data: Map = Deserialize::deserialize(deserializer)?; + let syntax_data: HashMap = Deserialize::deserialize(deserializer)?; for (key, style) in syntax_data { - 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()).map_err(de::Error::custom)?; - } - properties = font_properties_from_json(style).map_err(de::Error::custom)?; - } - _ => { - color = serde_json::from_value(style.clone()).map_err(de::Error::custom)?; - } - } - match result.binary_search_by(|(needle, _, _)| needle.cmp(&key)) { + match result.binary_search_by(|(needle, _)| needle.cmp(&key)) { Ok(i) | Err(i) => { - result.insert(i, (key, color, properties)); + result.insert(i, (key, style)); } } } @@ -463,131 +456,95 @@ where #[cfg(test)] mod tests { use super::*; - use gpui::fonts::{Properties as FontProperties, Style as FontStyle, Weight as FontWeight}; #[test] - fn test_parse_simple_theme() { - let assets = TestAssets(&[( - "themes/my-theme.toml", - r#" - [ui.tab.active] - background = 0x100000 - - [editor] - background = 0x00ed00 - line_number = 0xdddddd - - [syntax] - "beta.two" = 0xAABBCC - "alpha.one" = {color = 0x112233, weight = "bold"} - "gamma.three" = {weight = "light", italic = true} - "#, - )]); - - let registry = ThemeRegistry::new(assets); - let theme = registry.get("my-theme").unwrap(); - - assert_eq!( - theme.ui.active_tab.container.background_color, - Some(Color::from_u32(0x100000ff)) - ); - assert_eq!(theme.editor.background, Color::from_u32(0x00ed00ff)); - assert_eq!(theme.editor.line_number, Color::from_u32(0xddddddff)); - assert_eq!( - theme.syntax, - &[ - ( - "alpha.one".to_string(), - Color::from_u32(0x112233ff), - *FontProperties::new().weight(FontWeight::BOLD) - ), - ( - "beta.two".to_string(), - Color::from_u32(0xaabbccff), - *FontProperties::new().weight(FontWeight::NORMAL) - ), - ( - "gamma.three".to_string(), - Color::from_u32(0x00000000), - *FontProperties::new() - .weight(FontWeight::LIGHT) - .style(FontStyle::Italic), - ), - ] - ); - } - - #[test] - fn test_parse_extended_theme() { + fn test_theme_extension() { let assets = TestAssets(&[ ( "themes/_base.toml", - r#" - abstract = true + r##" + [ui.active_tab] + extends = "ui.tab" + border.color = "#666666" + text = "$bright_text" [ui.tab] - background = 0x111111 - text = "$variable_1" + extends = "ui.element" + text = "$dull_text" + + [ui.element] + background = "#111111" + border = {width = 2.0, color = "#00000000"} [editor] - background = 0x222222 - default_text = "$variable_2" - "#, + background = "#222222" + default_text = "$regular_text" + "##, ), ( "themes/light.toml", - r#" + r##" extends = "_base" [variables] - variable_1 = 0x333333 - variable_2 = 0x444444 - - [ui.tab] - background = 0x555555 + bright_text = "#ffffff" + regular_text = "#eeeeee" + dull_text = "#dddddd" [editor] - background = 0x666666 - "#, - ), - ( - "themes/dark.toml", - r#" - extends = "_base" - - [variables] - variable_1 = 0x555555 - variable_2 = 0x666666 - "#, + background = "#232323" + "##, ), ]); let registry = ThemeRegistry::new(assets); - let theme = registry.get("light").unwrap(); - + let theme_data = registry.load("light").unwrap(); assert_eq!( - theme.ui.tab.container.background_color, - Some(Color::from_u32(0x555555ff)) - ); - assert_eq!(theme.ui.tab.label.color, Color::from_u32(0x333333ff)); - assert_eq!(theme.editor.background, Color::from_u32(0x666666ff)); - assert_eq!(theme.editor.text, Color::from_u32(0x444444ff)); - - assert_eq!( - registry.list().collect::>(), - &["light".to_string(), "dark".to_string()] + theme_data.as_ref(), + &serde_json::json!({ + "ui": { + "active_tab": { + "background": "#111111", + "border": { + "width": 2.0, + "color": "#666666" + }, + "extends": "ui.tab", + "text": "#ffffff" + }, + "tab": { + "background": "#111111", + "border": { + "width": 2.0, + "color": "#00000000" + }, + "extends": "ui.element", + "text": "#dddddd" + }, + "element": { + "background": "#111111", + "border": { + "width": 2.0, + "color": "#00000000" + } + } + }, + "editor": { + "background": "#232323", + "default_text": "#eeeeee" + }, + "extends": "_base", + "variables": { + "bright_text": "#ffffff", + "regular_text": "#eeeeee", + "dull_text": "#dddddd" + } + }) ); } #[test] - fn test_parse_empty_theme() { - let assets = TestAssets(&[("themes/my-theme.toml", "")]); - let registry = ThemeRegistry::new(assets); - registry.get("my-theme").unwrap(); - } - - #[test] - fn test_theme_map() { + fn test_highlight_map() { let theme = Theme { ui: Default::default(), editor: Default::default(), @@ -600,7 +557,7 @@ mod tests { ("variable", Color::from_u32(0x600000ff)), ] .iter() - .map(|e| (e.0.to_string(), e.1, FontProperties::new())) + .map(|(name, color)| (name.to_string(), (*color).into())) .collect(), }; @@ -610,13 +567,10 @@ mod tests { "variable.builtin.self".to_string(), ]; - let map = ThemeMap::new(capture_names, &theme); - assert_eq!(theme.syntax_style_name(map.get(0)), Some("function")); - assert_eq!(theme.syntax_style_name(map.get(1)), Some("function.async")); - assert_eq!( - theme.syntax_style_name(map.get(2)), - Some("variable.builtin") - ); + let map = HighlightMap::new(capture_names, &theme); + assert_eq!(theme.highlight_name(map.get(0)), Some("function")); + assert_eq!(theme.highlight_name(map.get(1)), Some("function.async")); + assert_eq!(theme.highlight_name(map.get(2)), Some("variable.builtin")); } struct TestAssets(&'static [(&'static str, &'static str)]);