theme_registry.rs

  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}