1use anyhow::{anyhow, Context, Result};
2use gpui::{fonts, AssetSource, FontCache};
3use json::{Map, Value};
4use parking_lot::Mutex;
5use serde_json as json;
6use std::{collections::HashMap, fmt, mem, sync::Arc};
7
8use super::Theme;
9
10pub struct ThemeRegistry {
11 assets: Box<dyn AssetSource>,
12 themes: Mutex<HashMap<String, Arc<Theme>>>,
13 theme_data: Mutex<HashMap<String, Arc<Value>>>,
14 font_cache: Arc<FontCache>,
15}
16
17#[derive(Default)]
18struct KeyPathReferenceSet {
19 references: Vec<KeyPathReference>,
20 reference_ids_by_source: Vec<usize>,
21 reference_ids_by_target: Vec<usize>,
22 dependencies: Vec<(usize, usize)>,
23 dependency_counts: Vec<usize>,
24}
25
26#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
27struct KeyPathReference {
28 target: KeyPath,
29 source: KeyPath,
30}
31
32#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
33struct KeyPath(Vec<Key>);
34
35#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
36enum Key {
37 Array(usize),
38 Object(String),
39}
40
41impl ThemeRegistry {
42 pub fn new(source: impl AssetSource, font_cache: Arc<FontCache>) -> Arc<Self> {
43 Arc::new(Self {
44 assets: Box::new(source),
45 themes: Default::default(),
46 theme_data: Default::default(),
47 font_cache,
48 })
49 }
50
51 pub fn list(&self) -> impl Iterator<Item = String> {
52 self.assets.list("themes/").into_iter().filter_map(|path| {
53 let filename = path.strip_prefix("themes/")?;
54 let theme_name = filename.strip_suffix(".toml")?;
55 if theme_name.starts_with('_') {
56 None
57 } else {
58 Some(theme_name.to_string())
59 }
60 })
61 }
62
63 pub fn clear(&self) {
64 self.theme_data.lock().clear();
65 self.themes.lock().clear();
66 }
67
68 pub fn get(&self, name: &str) -> Result<Arc<Theme>> {
69 if let Some(theme) = self.themes.lock().get(name) {
70 return Ok(theme.clone());
71 }
72
73 let theme_data = self.load(name, true)?;
74 let mut theme = fonts::with_font_cache(self.font_cache.clone(), || {
75 serde_json::from_value::<Theme>(theme_data.as_ref().clone())
76 })?;
77
78 theme.name = name.into();
79 let theme = Arc::new(theme);
80 self.themes.lock().insert(name.to_string(), theme.clone());
81 Ok(theme)
82 }
83
84 fn load(&self, name: &str, evaluate_references: bool) -> Result<Arc<Value>> {
85 if let Some(data) = self.theme_data.lock().get(name) {
86 return Ok(data.clone());
87 }
88
89 let asset_path = format!("themes/{}.toml", name);
90 let source_code = self
91 .assets
92 .load(&asset_path)
93 .with_context(|| format!("failed to load theme file {}", asset_path))?;
94
95 let mut theme_data: Map<String, Value> = toml::from_slice(source_code.as_ref())
96 .with_context(|| format!("failed to parse {}.toml", name))?;
97
98 // If this theme extends another base theme, deeply merge it into the base theme's data
99 if let Some(base_name) = theme_data
100 .get("extends")
101 .and_then(|name| name.as_str())
102 .map(str::to_string)
103 {
104 let base_theme_data = self
105 .load(&base_name, false)
106 .with_context(|| format!("failed to load base theme {}", base_name))?
107 .as_ref()
108 .clone();
109 if let Value::Object(mut base_theme_object) = base_theme_data {
110 deep_merge_json(&mut base_theme_object, theme_data);
111 theme_data = base_theme_object;
112 }
113 }
114
115 // Find all of the key path references in the object, and then sort them according
116 // to their dependencies.
117 if evaluate_references {
118 let mut key_path = KeyPath::default();
119 let mut references = KeyPathReferenceSet::default();
120 for (key, value) in theme_data.iter() {
121 key_path.0.push(Key::Object(key.clone()));
122 find_references(value, &mut key_path, &mut references);
123 key_path.0.pop();
124 }
125 let sorted_references = references
126 .top_sort()
127 .map_err(|key_paths| anyhow!("cycle for key paths: {:?}", key_paths))?;
128
129 // Now update objects to include the fields of objects they extend
130 for KeyPathReference { source, target } in sorted_references {
131 if let Some(source) = value_at(&mut theme_data, &source).cloned() {
132 let target = value_at(&mut theme_data, &target).unwrap();
133 if let Value::Object(target_object) = target.take() {
134 if let Value::Object(mut source_object) = source {
135 deep_merge_json(&mut source_object, target_object);
136 *target = Value::Object(source_object);
137 } else {
138 Err(anyhow!("extended key path {} is not an object", source))?;
139 }
140 } else {
141 *target = source;
142 }
143 } else {
144 Err(anyhow!("invalid key path '{}'", source))?;
145 }
146 }
147 }
148
149 let result = Arc::new(Value::Object(theme_data));
150 self.theme_data
151 .lock()
152 .insert(name.to_string(), result.clone());
153
154 Ok(result)
155 }
156}
157
158impl KeyPathReferenceSet {
159 fn insert(&mut self, reference: KeyPathReference) {
160 let id = self.references.len();
161 let source_ix = self
162 .reference_ids_by_source
163 .binary_search_by_key(&&reference.source, |id| &self.references[*id].source)
164 .unwrap_or_else(|i| i);
165 let target_ix = self
166 .reference_ids_by_target
167 .binary_search_by_key(&&reference.target, |id| &self.references[*id].target)
168 .unwrap_or_else(|i| i);
169
170 self.populate_dependencies(id, &reference);
171 self.reference_ids_by_source.insert(source_ix, id);
172 self.reference_ids_by_target.insert(target_ix, id);
173 self.references.push(reference);
174 }
175
176 fn top_sort(mut self) -> Result<Vec<KeyPathReference>, Vec<KeyPath>> {
177 let mut results = Vec::with_capacity(self.references.len());
178 let mut root_ids = Vec::with_capacity(self.references.len());
179
180 // Find the initial set of references that have no dependencies.
181 for (id, dep_count) in self.dependency_counts.iter().enumerate() {
182 if *dep_count == 0 {
183 root_ids.push(id);
184 }
185 }
186
187 while results.len() < root_ids.len() {
188 // Just to guarantee a stable result when the inputs are randomized,
189 // sort references lexicographically in absence of any dependency relationship.
190 root_ids[results.len()..].sort_by_key(|id| &self.references[*id]);
191
192 let root_id = root_ids[results.len()];
193 let root = mem::take(&mut self.references[root_id]);
194 results.push(root);
195
196 // Remove this reference as a dependency from any of its dependent references.
197 if let Ok(dep_ix) = self
198 .dependencies
199 .binary_search_by_key(&root_id, |edge| edge.0)
200 {
201 let mut first_dep_ix = dep_ix;
202 let mut last_dep_ix = dep_ix + 1;
203 while first_dep_ix > 0 && self.dependencies[first_dep_ix - 1].0 == root_id {
204 first_dep_ix -= 1;
205 }
206 while last_dep_ix < self.dependencies.len()
207 && self.dependencies[last_dep_ix].0 == root_id
208 {
209 last_dep_ix += 1;
210 }
211
212 // If any reference no longer has any dependencies, then then mark it as a root.
213 // Preserve the references' original order where possible.
214 for (_, successor_id) in self.dependencies.drain(first_dep_ix..last_dep_ix) {
215 self.dependency_counts[successor_id] -= 1;
216 if self.dependency_counts[successor_id] == 0 {
217 root_ids.push(successor_id);
218 }
219 }
220 }
221 }
222
223 // If any references never became roots, then there are reference cycles
224 // in the set. Return an error containing all of the key paths that are
225 // directly involved in cycles.
226 if results.len() < self.references.len() {
227 let mut cycle_ref_ids = (0..self.references.len())
228 .filter(|id| !root_ids.contains(id))
229 .collect::<Vec<_>>();
230
231 // Iteratively remove any references that have no dependencies,
232 // so that the error will only indicate which key paths are directly
233 // involved in the cycles.
234 let mut done = false;
235 while !done {
236 done = true;
237 cycle_ref_ids.retain(|id| {
238 if self.dependencies.iter().any(|dep| dep.0 == *id) {
239 true
240 } else {
241 done = false;
242 self.dependencies.retain(|dep| dep.1 != *id);
243 false
244 }
245 });
246 }
247
248 let mut cycle_key_paths = Vec::new();
249 for id in cycle_ref_ids {
250 let reference = &self.references[id];
251 cycle_key_paths.push(reference.target.clone());
252 cycle_key_paths.push(reference.source.clone());
253 }
254 cycle_key_paths.sort_unstable();
255 return Err(cycle_key_paths);
256 }
257
258 Ok(results)
259 }
260
261 fn populate_dependencies(&mut self, new_id: usize, new_reference: &KeyPathReference) {
262 self.dependency_counts.push(0);
263
264 // If an existing reference's source path starts with the new reference's
265 // target path, then insert this new reference before that existing reference.
266 for id in Self::reference_ids_for_key_path(
267 &new_reference.target.0,
268 &self.references,
269 &self.reference_ids_by_source,
270 KeyPathReference::source,
271 KeyPath::starts_with,
272 ) {
273 Self::add_dependency(
274 (new_id, id),
275 &mut self.dependencies,
276 &mut self.dependency_counts,
277 );
278 }
279
280 // If an existing reference's target path starts with the new reference's
281 // source path, then insert this new reference after that existing reference.
282 for id in Self::reference_ids_for_key_path(
283 &new_reference.source.0,
284 &self.references,
285 &self.reference_ids_by_target,
286 KeyPathReference::target,
287 KeyPath::starts_with,
288 ) {
289 Self::add_dependency(
290 (id, new_id),
291 &mut self.dependencies,
292 &mut self.dependency_counts,
293 );
294 }
295
296 // If an existing reference's source path is a prefix of the new reference's
297 // target path, then insert this new reference before that existing reference.
298 for prefix in new_reference.target.prefixes() {
299 for id in Self::reference_ids_for_key_path(
300 prefix,
301 &self.references,
302 &self.reference_ids_by_source,
303 KeyPathReference::source,
304 PartialEq::eq,
305 ) {
306 Self::add_dependency(
307 (new_id, id),
308 &mut self.dependencies,
309 &mut self.dependency_counts,
310 );
311 }
312 }
313
314 // If an existing reference's target path is a prefix of the new reference's
315 // source path, then insert this new reference after that existing reference.
316 for prefix in new_reference.source.prefixes() {
317 for id in Self::reference_ids_for_key_path(
318 prefix,
319 &self.references,
320 &self.reference_ids_by_target,
321 KeyPathReference::target,
322 PartialEq::eq,
323 ) {
324 Self::add_dependency(
325 (id, new_id),
326 &mut self.dependencies,
327 &mut self.dependency_counts,
328 );
329 }
330 }
331 }
332
333 // Find all existing references that satisfy a given predicate with respect
334 // to a given key path. Use a sorted array of reference ids in order to avoid
335 // performing unnecessary comparisons.
336 fn reference_ids_for_key_path<'a>(
337 key_path: &[Key],
338 references: &[KeyPathReference],
339 sorted_reference_ids: &'a [usize],
340 reference_attribute: impl Fn(&KeyPathReference) -> &KeyPath,
341 predicate: impl Fn(&KeyPath, &[Key]) -> bool,
342 ) -> impl Iterator<Item = usize> + 'a {
343 let ix = sorted_reference_ids
344 .binary_search_by_key(&key_path, |id| &reference_attribute(&references[*id]).0)
345 .unwrap_or_else(|i| i);
346
347 let mut start_ix = ix;
348 while start_ix > 0 {
349 let reference_id = sorted_reference_ids[start_ix - 1];
350 let reference = &references[reference_id];
351 if !predicate(&reference_attribute(reference), key_path) {
352 break;
353 }
354 start_ix -= 1;
355 }
356
357 let mut end_ix = ix;
358 while end_ix < sorted_reference_ids.len() {
359 let reference_id = sorted_reference_ids[end_ix];
360 let reference = &references[reference_id];
361 if !predicate(&reference_attribute(reference), key_path) {
362 break;
363 }
364 end_ix += 1;
365 }
366
367 sorted_reference_ids[start_ix..end_ix].iter().copied()
368 }
369
370 fn add_dependency(
371 (predecessor, successor): (usize, usize),
372 dependencies: &mut Vec<(usize, usize)>,
373 dependency_counts: &mut Vec<usize>,
374 ) {
375 let dependency = (predecessor, successor);
376 if let Err(i) = dependencies.binary_search(&dependency) {
377 dependencies.insert(i, dependency);
378 }
379 dependency_counts[successor] += 1;
380 }
381}
382
383impl KeyPathReference {
384 fn source(&self) -> &KeyPath {
385 &self.source
386 }
387
388 fn target(&self) -> &KeyPath {
389 &self.target
390 }
391}
392
393impl KeyPath {
394 fn new(string: &str) -> Self {
395 Self(
396 string
397 .split(".")
398 .map(|key| Key::Object(key.to_string()))
399 .collect(),
400 )
401 }
402
403 fn starts_with(&self, other: &[Key]) -> bool {
404 self.0.starts_with(&other)
405 }
406
407 fn prefixes(&self) -> impl Iterator<Item = &[Key]> {
408 (1..self.0.len()).map(move |end_ix| &self.0[0..end_ix])
409 }
410}
411
412impl PartialEq<[Key]> for KeyPath {
413 fn eq(&self, other: &[Key]) -> bool {
414 self.0.eq(other)
415 }
416}
417
418impl fmt::Debug for KeyPathReference {
419 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420 write!(
421 f,
422 "KeyPathReference {{ {} <- {} }}",
423 self.target, self.source
424 )
425 }
426}
427
428impl fmt::Display for KeyPath {
429 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430 for (i, key) in self.0.iter().enumerate() {
431 match key {
432 Key::Array(index) => write!(f, "[{}]", index)?,
433 Key::Object(key) => {
434 if i > 0 {
435 ".".fmt(f)?;
436 }
437 key.fmt(f)?;
438 }
439 }
440 }
441 Ok(())
442 }
443}
444
445fn deep_merge_json(base: &mut Map<String, Value>, extension: Map<String, Value>) {
446 for (key, extension_value) in extension {
447 if let Value::Object(extension_object) = extension_value {
448 if let Some(base_object) = base.get_mut(&key).and_then(|value| value.as_object_mut()) {
449 deep_merge_json(base_object, extension_object);
450 } else {
451 base.insert(key, Value::Object(extension_object));
452 }
453 } else {
454 base.insert(key, extension_value);
455 }
456 }
457}
458
459fn find_references(value: &Value, key_path: &mut KeyPath, references: &mut KeyPathReferenceSet) {
460 match value {
461 Value::Array(vec) => {
462 for (ix, value) in vec.iter().enumerate() {
463 key_path.0.push(Key::Array(ix));
464 find_references(value, key_path, references);
465 key_path.0.pop();
466 }
467 }
468 Value::Object(map) => {
469 for (key, value) in map.iter() {
470 if key == "extends" {
471 if let Some(source_path) = value.as_str().and_then(|s| s.strip_prefix("$")) {
472 references.insert(KeyPathReference {
473 source: KeyPath::new(source_path),
474 target: key_path.clone(),
475 });
476 }
477 } else {
478 key_path.0.push(Key::Object(key.to_string()));
479 find_references(value, key_path, references);
480 key_path.0.pop();
481 }
482 }
483 }
484 Value::String(string) => {
485 if let Some(source_path) = string.strip_prefix("$") {
486 references.insert(KeyPathReference {
487 source: KeyPath::new(source_path),
488 target: key_path.clone(),
489 });
490 }
491 }
492 _ => {}
493 }
494}
495
496fn value_at<'a>(object: &'a mut Map<String, Value>, key_path: &KeyPath) -> Option<&'a mut Value> {
497 let mut key_path = key_path.0.iter();
498 if let Some(Key::Object(first_key)) = key_path.next() {
499 let mut cur_value = object.get_mut(first_key);
500 for key in key_path {
501 if let Some(value) = cur_value {
502 match key {
503 Key::Array(ix) => cur_value = value.get_mut(ix),
504 Key::Object(key) => cur_value = value.get_mut(key),
505 }
506 } else {
507 return None;
508 }
509 }
510 cur_value
511 } else {
512 None
513 }
514}
515
516#[cfg(test)]
517mod tests {
518 use super::*;
519 use crate::{assets::Assets, theme::DEFAULT_THEME_NAME};
520 use gpui::MutableAppContext;
521 use rand::{prelude::StdRng, Rng};
522
523 #[gpui::test]
524 fn test_bundled_themes(cx: &mut MutableAppContext) {
525 let registry = ThemeRegistry::new(Assets, cx.font_cache().clone());
526 let mut has_default_theme = false;
527 for theme_name in registry.list() {
528 let theme = registry.get(&theme_name).unwrap();
529 if theme.name == DEFAULT_THEME_NAME {
530 has_default_theme = true;
531 }
532 assert_eq!(theme.name, theme_name);
533 }
534 assert!(has_default_theme);
535 }
536
537 #[gpui::test]
538 fn test_theme_extension(cx: &mut MutableAppContext) {
539 let assets = TestAssets(&[
540 (
541 "themes/_base.toml",
542 r##"
543 [ui.active_tab]
544 extends = "$ui.tab"
545 border.color = "#666666"
546 text = "$text_colors.bright"
547
548 [ui.tab]
549 extends = "$ui.element"
550 text = "$text_colors.dull"
551
552 [ui.element]
553 background = "#111111"
554 border = {width = 2.0, color = "#00000000"}
555
556 [editor]
557 background = "#222222"
558 default_text = "$text_colors.regular"
559 "##,
560 ),
561 (
562 "themes/light.toml",
563 r##"
564 extends = "_base"
565
566 [text_colors]
567 bright = "#ffffff"
568 regular = "#eeeeee"
569 dull = "#dddddd"
570
571 [editor]
572 background = "#232323"
573 "##,
574 ),
575 ]);
576
577 let registry = ThemeRegistry::new(assets, cx.font_cache().clone());
578 let theme_data = registry.load("light", true).unwrap();
579 assert_eq!(
580 theme_data.as_ref(),
581 &serde_json::json!({
582 "ui": {
583 "active_tab": {
584 "background": "#111111",
585 "border": {
586 "width": 2.0,
587 "color": "#666666"
588 },
589 "extends": "$ui.tab",
590 "text": "#ffffff"
591 },
592 "tab": {
593 "background": "#111111",
594 "border": {
595 "width": 2.0,
596 "color": "#00000000"
597 },
598 "extends": "$ui.element",
599 "text": "#dddddd"
600 },
601 "element": {
602 "background": "#111111",
603 "border": {
604 "width": 2.0,
605 "color": "#00000000"
606 }
607 }
608 },
609 "editor": {
610 "background": "#232323",
611 "default_text": "#eeeeee"
612 },
613 "extends": "_base",
614 "text_colors": {
615 "bright": "#ffffff",
616 "regular": "#eeeeee",
617 "dull": "#dddddd"
618 }
619 })
620 );
621 }
622
623 #[test]
624 fn test_key_path_reference_set_simple() {
625 let input_references = build_refs(&[
626 ("r", "a"),
627 ("a.b.c", "d"),
628 ("d.e", "f"),
629 ("t.u", "v"),
630 ("v.w", "x"),
631 ("v.y", "x"),
632 ("d.h", "i"),
633 ("v.z", "x"),
634 ("f.g", "d.h"),
635 ]);
636 let expected_references = build_refs(&[
637 ("d.h", "i"),
638 ("f.g", "d.h"),
639 ("d.e", "f"),
640 ("a.b.c", "d"),
641 ("r", "a"),
642 ("v.w", "x"),
643 ("v.y", "x"),
644 ("v.z", "x"),
645 ("t.u", "v"),
646 ])
647 .collect::<Vec<_>>();
648
649 let mut reference_set = KeyPathReferenceSet::default();
650 for reference in input_references {
651 reference_set.insert(reference);
652 }
653 assert_eq!(reference_set.top_sort().unwrap(), expected_references);
654 }
655
656 #[test]
657 fn test_key_path_reference_set_with_cycles() {
658 let input_references = build_refs(&[
659 ("x", "a.b"),
660 ("y", "x.c"),
661 ("a.b.c", "d.e"),
662 ("d.e.f", "g.h"),
663 ("g.h.i", "a"),
664 ]);
665
666 let mut reference_set = KeyPathReferenceSet::default();
667 for reference in input_references {
668 reference_set.insert(reference);
669 }
670
671 assert_eq!(
672 reference_set.top_sort().unwrap_err(),
673 &[
674 KeyPath::new("a"),
675 KeyPath::new("a.b.c"),
676 KeyPath::new("d.e"),
677 KeyPath::new("d.e.f"),
678 KeyPath::new("g.h"),
679 KeyPath::new("g.h.i"),
680 ]
681 );
682 }
683
684 #[gpui::test(iterations = 20)]
685 async fn test_key_path_reference_set_random(mut rng: StdRng) {
686 let examples: &[&[_]] = &[
687 &[
688 ("n.d.h", "i"),
689 ("f.g", "n.d.h"),
690 ("n.d.e", "f"),
691 ("a.b.c", "n.d"),
692 ("r", "a"),
693 ("q.q.q", "r.s"),
694 ("r.t", "q"),
695 ("x.x", "r.r"),
696 ("v.w", "x"),
697 ("v.y", "x"),
698 ("v.z", "x"),
699 ("t.u", "v"),
700 ],
701 &[
702 ("w.x.y.z", "t.u.z"),
703 ("x", "w.x"),
704 ("a.b.c1", "x.b1.c"),
705 ("a.b.c2", "x.b2.c"),
706 ],
707 &[
708 ("x.y", "m.n.n.o.q"),
709 ("x.y.z", "m.n.n.o.p"),
710 ("u.v.w", "x.y.z"),
711 ("a.b.c.d", "u.v"),
712 ("a.b.c.d.e", "u.v"),
713 ("a.b.c.d.f", "u.v"),
714 ("a.b.c.d.g", "u.v"),
715 ],
716 ];
717
718 for example in examples {
719 let expected_references = build_refs(example).collect::<Vec<_>>();
720 let mut input_references = expected_references.clone();
721 input_references.sort_by_key(|_| rng.gen_range(0..1000));
722 let mut reference_set = KeyPathReferenceSet::default();
723 for reference in input_references {
724 reference_set.insert(reference);
725 }
726 assert_eq!(reference_set.top_sort().unwrap(), expected_references);
727 }
728 }
729
730 fn build_refs<'a>(rows: &'a [(&str, &str)]) -> impl Iterator<Item = KeyPathReference> + 'a {
731 rows.iter().map(|(target, source)| KeyPathReference {
732 target: KeyPath::new(target),
733 source: KeyPath::new(source),
734 })
735 }
736
737 struct TestAssets(&'static [(&'static str, &'static str)]);
738
739 impl AssetSource for TestAssets {
740 fn load(&self, path: &str) -> Result<std::borrow::Cow<[u8]>> {
741 if let Some(row) = self.0.iter().find(|e| e.0 == path) {
742 Ok(row.1.as_bytes().into())
743 } else {
744 Err(anyhow!("no such path {}", path))
745 }
746 }
747
748 fn list(&self, prefix: &str) -> Vec<std::borrow::Cow<'static, str>> {
749 self.0
750 .iter()
751 .copied()
752 .filter_map(|(path, _)| {
753 if path.starts_with(prefix) {
754 Some(path.into())
755 } else {
756 None
757 }
758 })
759 .collect()
760 }
761 }
762}