Get new theme structure working
* Fix precedence of extends directives * Always group color with font properties for text theming Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
co-authored by
Nathan Sobo
parent
5761756fb4
commit
802f1f4e78
+7
-7
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Self::Item> {
|
||||
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())
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<Self::Item> {
|
||||
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.
|
||||
|
||||
@@ -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<Snapshot>);
|
||||
@@ -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<Self::Item> {
|
||||
if self.chunk.is_empty() {
|
||||
|
||||
@@ -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<Self::Item> {
|
||||
if self.output_position.row() >= self.max_output_row {
|
||||
|
||||
+9
-10
@@ -141,11 +141,15 @@ impl FileFinder {
|
||||
index: usize,
|
||||
cx: &AppContext,
|
||||
) -> Option<ElementBox> {
|
||||
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())
|
||||
|
||||
+8
-8
@@ -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<ThemeMap>,
|
||||
pub highlight_map: Mutex<HighlightMap>,
|
||||
}
|
||||
|
||||
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(),
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
+8
-3
@@ -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<Settings>, watch::Receiver<Settings>)> {
|
||||
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,
|
||||
)?))
|
||||
}
|
||||
|
||||
+102
-148
@@ -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<dyn AssetSource>,
|
||||
@@ -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<String, Value>, extension: Map<String, Value>)
|
||||
}
|
||||
}
|
||||
|
||||
#[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<Key>,
|
||||
target_path: Vec<Key>,
|
||||
@@ -429,30 +435,17 @@ fn validate_variable_name(name: &str) -> bool {
|
||||
|
||||
pub fn deserialize_syntax_theme<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Vec<(String, Color, FontProperties)>, D::Error>
|
||||
) -> Result<Vec<(String, TextStyle)>, 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<String, Value> = Deserialize::deserialize(deserializer)?;
|
||||
let syntax_data: HashMap<String, TextStyle> = 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::<Vec<_>>(),
|
||||
&["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)]);
|
||||
|
||||
Reference in New Issue
Block a user