theme.rs

  1use anyhow::{anyhow, Context, Result};
  2use gpui::{
  3    color::Color,
  4    elements::{ContainerStyle, LabelStyle},
  5    fonts::TextStyle,
  6    AssetSource,
  7};
  8use json::{Map, Value};
  9use parking_lot::Mutex;
 10use serde::{Deserialize, Deserializer};
 11use serde_json as json;
 12use std::{collections::HashMap, fmt, mem, sync::Arc};
 13
 14const DEFAULT_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX);
 15pub const DEFAULT_THEME_NAME: &'static str = "dark";
 16
 17pub struct ThemeRegistry {
 18    assets: Box<dyn AssetSource>,
 19    themes: Mutex<HashMap<String, Arc<Theme>>>,
 20    theme_data: Mutex<HashMap<String, Arc<Value>>>,
 21}
 22
 23#[derive(Clone, Debug)]
 24pub struct HighlightMap(Arc<[HighlightId]>);
 25
 26#[derive(Clone, Copy, Debug)]
 27pub struct HighlightId(u32);
 28
 29#[derive(Debug, Default, Deserialize)]
 30pub struct Theme {
 31    #[serde(default)]
 32    pub name: String,
 33    pub ui: Ui,
 34    pub editor: Editor,
 35    #[serde(deserialize_with = "deserialize_syntax_theme")]
 36    pub syntax: Vec<(String, TextStyle)>,
 37}
 38
 39#[derive(Debug, Default, Deserialize)]
 40pub struct Ui {
 41    pub background: Color,
 42    pub tab: Tab,
 43    pub active_tab: Tab,
 44    pub selector: Selector,
 45}
 46
 47#[derive(Debug, Deserialize)]
 48pub struct Editor {
 49    pub background: Color,
 50    pub gutter_background: Color,
 51    pub active_line_background: Color,
 52    pub line_number: Color,
 53    pub line_number_active: Color,
 54    pub text: Color,
 55    pub replicas: Vec<Replica>,
 56}
 57
 58#[derive(Clone, Copy, Debug, Default, Deserialize)]
 59pub struct Replica {
 60    pub cursor: Color,
 61    pub selection: Color,
 62}
 63
 64#[derive(Debug, Default, Deserialize)]
 65pub struct Tab {
 66    #[serde(flatten)]
 67    pub container: ContainerStyle,
 68    #[serde(flatten)]
 69    pub label: LabelStyle,
 70    pub icon_close: Color,
 71    pub icon_dirty: Color,
 72    pub icon_conflict: Color,
 73}
 74
 75#[derive(Debug, Default, Deserialize)]
 76pub struct Selector {
 77    #[serde(flatten)]
 78    pub container: ContainerStyle,
 79    #[serde(flatten)]
 80    pub label: LabelStyle,
 81
 82    pub item: SelectorItem,
 83    pub active_item: SelectorItem,
 84}
 85
 86#[derive(Debug, Default, Deserialize)]
 87pub struct SelectorItem {
 88    #[serde(flatten)]
 89    pub container: ContainerStyle,
 90    #[serde(flatten)]
 91    pub label: LabelStyle,
 92}
 93
 94#[derive(Default)]
 95struct KeyPathReferenceSet {
 96    references: Vec<KeyPathReference>,
 97    reference_ids_by_source: Vec<usize>,
 98    reference_ids_by_target: Vec<usize>,
 99    dependencies: Vec<(usize, usize)>,
100    dependency_counts: Vec<usize>,
101}
102
103#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
104struct KeyPathReference {
105    target: KeyPath,
106    source: KeyPath,
107}
108
109#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
110struct KeyPath(Vec<Key>);
111
112#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
113enum Key {
114    Array(usize),
115    Object(String),
116}
117
118impl Default for Editor {
119    fn default() -> Self {
120        Self {
121            background: Default::default(),
122            gutter_background: Default::default(),
123            active_line_background: Default::default(),
124            line_number: Default::default(),
125            line_number_active: Default::default(),
126            text: Default::default(),
127            replicas: vec![Replica::default()],
128        }
129    }
130}
131
132impl ThemeRegistry {
133    pub fn new(source: impl AssetSource) -> Arc<Self> {
134        Arc::new(Self {
135            assets: Box::new(source),
136            themes: Default::default(),
137            theme_data: Default::default(),
138        })
139    }
140
141    pub fn list(&self) -> impl Iterator<Item = String> {
142        self.assets.list("themes/").into_iter().filter_map(|path| {
143            let filename = path.strip_prefix("themes/")?;
144            let theme_name = filename.strip_suffix(".toml")?;
145            if theme_name.starts_with('_') {
146                None
147            } else {
148                Some(theme_name.to_string())
149            }
150        })
151    }
152
153    pub fn clear(&self) {
154        self.theme_data.lock().clear();
155        self.themes.lock().clear();
156    }
157
158    pub fn get(&self, name: &str) -> Result<Arc<Theme>> {
159        if let Some(theme) = self.themes.lock().get(name) {
160            return Ok(theme.clone());
161        }
162
163        let theme_data = self.load(name, true)?;
164        let mut theme = serde_json::from_value::<Theme>(theme_data.as_ref().clone())?;
165        theme.name = name.into();
166        let theme = Arc::new(theme);
167        self.themes.lock().insert(name.to_string(), theme.clone());
168        Ok(theme)
169    }
170
171    fn load(&self, name: &str, evaluate_references: bool) -> Result<Arc<Value>> {
172        if let Some(data) = self.theme_data.lock().get(name) {
173            return Ok(data.clone());
174        }
175
176        let asset_path = format!("themes/{}.toml", name);
177        let source_code = self
178            .assets
179            .load(&asset_path)
180            .with_context(|| format!("failed to load theme file {}", asset_path))?;
181
182        let mut theme_data: Map<String, Value> = toml::from_slice(source_code.as_ref())
183            .with_context(|| format!("failed to parse {}.toml", name))?;
184
185        // If this theme extends another base theme, deeply merge it into the base theme's data
186        if let Some(base_name) = theme_data
187            .get("extends")
188            .and_then(|name| name.as_str())
189            .map(str::to_string)
190        {
191            let base_theme_data = self
192                .load(&base_name, false)
193                .with_context(|| format!("failed to load base theme {}", base_name))?
194                .as_ref()
195                .clone();
196            if let Value::Object(mut base_theme_object) = base_theme_data {
197                deep_merge_json(&mut base_theme_object, theme_data);
198                theme_data = base_theme_object;
199            }
200        }
201
202        // Find all of the key path references in the object, and then sort them according
203        // to their dependencies.
204        if evaluate_references {
205            let mut key_path = KeyPath::default();
206            let mut references = KeyPathReferenceSet::default();
207            for (key, value) in theme_data.iter() {
208                key_path.0.push(Key::Object(key.clone()));
209                find_references(value, &mut key_path, &mut references);
210                key_path.0.pop();
211            }
212            let sorted_references = references
213                .top_sort()
214                .map_err(|key_paths| anyhow!("cycle for key paths: {:?}", key_paths))?;
215
216            // Now update objects to include the fields of objects they extend
217            for KeyPathReference { source, target } in sorted_references {
218                if let Some(source) = value_at(&mut theme_data, &source).cloned() {
219                    let target = value_at(&mut theme_data, &target).unwrap();
220                    if let Value::Object(target_object) = target.take() {
221                        if let Value::Object(mut source_object) = source {
222                            deep_merge_json(&mut source_object, target_object);
223                            *target = Value::Object(source_object);
224                        } else {
225                            Err(anyhow!("extended key path {} is not an object", source))?;
226                        }
227                    } else {
228                        *target = source;
229                    }
230                } else {
231                    Err(anyhow!("invalid key path '{}'", source))?;
232                }
233            }
234        }
235
236        let result = Arc::new(Value::Object(theme_data));
237        self.theme_data
238            .lock()
239            .insert(name.to_string(), result.clone());
240
241        Ok(result)
242    }
243}
244
245impl Theme {
246    pub fn highlight_style(&self, id: HighlightId) -> TextStyle {
247        self.syntax
248            .get(id.0 as usize)
249            .map(|entry| entry.1.clone())
250            .unwrap_or_else(|| TextStyle {
251                color: self.editor.text,
252                font_properties: Default::default(),
253            })
254    }
255
256    #[cfg(test)]
257    pub fn highlight_name(&self, id: HighlightId) -> Option<&str> {
258        self.syntax.get(id.0 as usize).map(|e| e.0.as_str())
259    }
260}
261
262impl HighlightMap {
263    pub fn new(capture_names: &[String], theme: &Theme) -> Self {
264        // For each capture name in the highlight query, find the longest
265        // key in the theme's syntax styles that matches all of the
266        // dot-separated components of the capture name.
267        HighlightMap(
268            capture_names
269                .iter()
270                .map(|capture_name| {
271                    theme
272                        .syntax
273                        .iter()
274                        .enumerate()
275                        .filter_map(|(i, (key, _))| {
276                            let mut len = 0;
277                            let capture_parts = capture_name.split('.');
278                            for key_part in key.split('.') {
279                                if capture_parts.clone().any(|part| part == key_part) {
280                                    len += 1;
281                                } else {
282                                    return None;
283                                }
284                            }
285                            Some((i, len))
286                        })
287                        .max_by_key(|(_, len)| *len)
288                        .map_or(DEFAULT_HIGHLIGHT_ID, |(i, _)| HighlightId(i as u32))
289                })
290                .collect(),
291        )
292    }
293
294    pub fn get(&self, capture_id: u32) -> HighlightId {
295        self.0
296            .get(capture_id as usize)
297            .copied()
298            .unwrap_or(DEFAULT_HIGHLIGHT_ID)
299    }
300}
301
302impl KeyPathReferenceSet {
303    fn insert(&mut self, reference: KeyPathReference) {
304        let id = self.references.len();
305        let source_ix = self
306            .reference_ids_by_source
307            .binary_search_by_key(&&reference.source, |id| &self.references[*id].source)
308            .unwrap_or_else(|i| i);
309        let target_ix = self
310            .reference_ids_by_target
311            .binary_search_by_key(&&reference.target, |id| &self.references[*id].target)
312            .unwrap_or_else(|i| i);
313
314        self.populate_dependencies(id, &reference);
315        self.reference_ids_by_source.insert(source_ix, id);
316        self.reference_ids_by_target.insert(target_ix, id);
317        self.references.push(reference);
318    }
319
320    fn top_sort(mut self) -> Result<Vec<KeyPathReference>, Vec<KeyPath>> {
321        let mut results = Vec::with_capacity(self.references.len());
322        let mut root_ids = Vec::with_capacity(self.references.len());
323
324        // Find the initial set of references that have no dependencies.
325        for (id, dep_count) in self.dependency_counts.iter().enumerate() {
326            if *dep_count == 0 {
327                root_ids.push(id);
328            }
329        }
330
331        root_ids.sort_by_key(|id| &self.references[*id]);
332
333        while results.len() < root_ids.len() {
334            let root_id = root_ids[results.len()];
335            let root = mem::take(&mut self.references[root_id]);
336            results.push(root);
337
338            // Remove this reference as a dependency from any of its dependent references.
339            if let Ok(dep_ix) = self
340                .dependencies
341                .binary_search_by_key(&root_id, |edge| edge.0)
342            {
343                let mut first_dep_ix = dep_ix;
344                let mut last_dep_ix = dep_ix + 1;
345                while first_dep_ix > 0 && self.dependencies[first_dep_ix - 1].0 == root_id {
346                    first_dep_ix -= 1;
347                }
348                while last_dep_ix < self.dependencies.len()
349                    && self.dependencies[last_dep_ix].0 == root_id
350                {
351                    last_dep_ix += 1;
352                }
353
354                // If any reference no longer has any dependencies, then then mark it as a root.
355                // Preserve the references' original order where possible.
356                for (_, successor_id) in self.dependencies.drain(first_dep_ix..last_dep_ix) {
357                    self.dependency_counts[successor_id] -= 1;
358                    if self.dependency_counts[successor_id] == 0 {
359                        if let Err(ix) = root_ids[results.len()..].binary_search(&successor_id) {
360                            root_ids.insert(results.len() + ix, successor_id);
361                        }
362                    }
363                }
364
365                root_ids[results.len()..].sort_by_key(|id| &self.references[*id]);
366            }
367        }
368
369        // If any references never became roots, then there are reference cycles
370        // in the set. Return an error containing all of the key paths that are
371        // directly involved in cycles.
372        if results.len() < self.references.len() {
373            let mut cycle_ref_ids = (0..self.references.len())
374                .filter(|id| !root_ids.contains(id))
375                .collect::<Vec<_>>();
376
377            // Iteratively remove any references that have no dependencies,
378            // so that the error will only indicate which key paths are directly
379            // involved in the cycles.
380            let mut done = false;
381            while !done {
382                done = true;
383                cycle_ref_ids.retain(|id| {
384                    if self.dependencies.iter().any(|dep| dep.0 == *id) {
385                        true
386                    } else {
387                        done = false;
388                        self.dependencies.retain(|dep| dep.1 != *id);
389                        false
390                    }
391                });
392            }
393
394            let mut cycle_key_paths = Vec::new();
395            for id in cycle_ref_ids {
396                let reference = &self.references[id];
397                cycle_key_paths.push(reference.target.clone());
398                cycle_key_paths.push(reference.source.clone());
399            }
400            cycle_key_paths.sort_unstable();
401            return Err(cycle_key_paths);
402        }
403
404        Ok(results)
405    }
406
407    fn populate_dependencies(&mut self, new_id: usize, new_reference: &KeyPathReference) {
408        self.dependency_counts.push(0);
409
410        // If an existing reference's source path starts with the new reference's
411        // target path, then insert this new reference before that existing reference.
412        for id in Self::reference_ids_for_key_path(
413            &new_reference.target.0,
414            &self.references,
415            &self.reference_ids_by_source,
416            KeyPathReference::source,
417            KeyPath::starts_with,
418        ) {
419            Self::add_dependency(
420                (new_id, id),
421                &mut self.dependencies,
422                &mut self.dependency_counts,
423            );
424        }
425
426        // If an existing reference's target path starts with the new reference's
427        // source path, then insert this new reference after that existing reference.
428        for id in Self::reference_ids_for_key_path(
429            &new_reference.source.0,
430            &self.references,
431            &self.reference_ids_by_target,
432            KeyPathReference::target,
433            KeyPath::starts_with,
434        ) {
435            Self::add_dependency(
436                (id, new_id),
437                &mut self.dependencies,
438                &mut self.dependency_counts,
439            );
440        }
441
442        // If an existing reference's source path is a prefix of the new reference's
443        // target path, then insert this new reference before that existing reference.
444        for prefix in new_reference.target.prefixes() {
445            for id in Self::reference_ids_for_key_path(
446                prefix,
447                &self.references,
448                &self.reference_ids_by_source,
449                KeyPathReference::source,
450                PartialEq::eq,
451            ) {
452                Self::add_dependency(
453                    (new_id, id),
454                    &mut self.dependencies,
455                    &mut self.dependency_counts,
456                );
457            }
458        }
459
460        // If an existing reference's target path is a prefix of the new reference's
461        // source path, then insert this new reference after that existing reference.
462        for prefix in new_reference.source.prefixes() {
463            for id in Self::reference_ids_for_key_path(
464                prefix,
465                &self.references,
466                &self.reference_ids_by_target,
467                KeyPathReference::target,
468                PartialEq::eq,
469            ) {
470                Self::add_dependency(
471                    (id, new_id),
472                    &mut self.dependencies,
473                    &mut self.dependency_counts,
474                );
475            }
476        }
477    }
478
479    // Find all existing references that satisfy a given predicate with respect
480    // to a given key path. Use a sorted array of reference ids in order to avoid
481    // performing unnecessary comparisons.
482    fn reference_ids_for_key_path<'a>(
483        key_path: &[Key],
484        references: &[KeyPathReference],
485        sorted_reference_ids: &'a [usize],
486        reference_attribute: impl Fn(&KeyPathReference) -> &KeyPath,
487        predicate: impl Fn(&KeyPath, &[Key]) -> bool,
488    ) -> impl Iterator<Item = usize> + 'a {
489        let ix = sorted_reference_ids
490            .binary_search_by_key(&key_path, |id| &reference_attribute(&references[*id]).0)
491            .unwrap_or_else(|i| i);
492
493        let mut start_ix = ix;
494        while start_ix > 0 {
495            let reference_id = sorted_reference_ids[start_ix - 1];
496            let reference = &references[reference_id];
497            if !predicate(&reference_attribute(reference), key_path) {
498                break;
499            }
500            start_ix -= 1;
501        }
502
503        let mut end_ix = ix;
504        while end_ix < sorted_reference_ids.len() {
505            let reference_id = sorted_reference_ids[end_ix];
506            let reference = &references[reference_id];
507            if !predicate(&reference_attribute(reference), key_path) {
508                break;
509            }
510            end_ix += 1;
511        }
512
513        sorted_reference_ids[start_ix..end_ix].iter().copied()
514    }
515
516    fn add_dependency(
517        (predecessor, successor): (usize, usize),
518        dependencies: &mut Vec<(usize, usize)>,
519        dependency_counts: &mut Vec<usize>,
520    ) {
521        let dependency = (predecessor, successor);
522        if let Err(i) = dependencies.binary_search(&dependency) {
523            dependencies.insert(i, dependency);
524        }
525        dependency_counts[successor] += 1;
526    }
527}
528
529impl KeyPathReference {
530    fn source(&self) -> &KeyPath {
531        &self.source
532    }
533
534    fn target(&self) -> &KeyPath {
535        &self.target
536    }
537}
538
539impl KeyPath {
540    fn new(string: &str) -> Self {
541        Self(
542            string
543                .split(".")
544                .map(|key| Key::Object(key.to_string()))
545                .collect(),
546        )
547    }
548
549    fn starts_with(&self, other: &[Key]) -> bool {
550        self.0.starts_with(&other)
551    }
552
553    fn prefixes(&self) -> impl Iterator<Item = &[Key]> {
554        (1..self.0.len()).map(move |end_ix| &self.0[0..end_ix])
555    }
556}
557
558impl PartialEq<[Key]> for KeyPath {
559    fn eq(&self, other: &[Key]) -> bool {
560        self.0.eq(other)
561    }
562}
563
564impl fmt::Debug for KeyPathReference {
565    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
566        write!(
567            f,
568            "KeyPathReference {{ {} <- {} }}",
569            self.target, self.source
570        )
571    }
572}
573
574impl fmt::Display for KeyPath {
575    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
576        for (i, key) in self.0.iter().enumerate() {
577            match key {
578                Key::Array(index) => write!(f, "[{}]", index)?,
579                Key::Object(key) => {
580                    if i > 0 {
581                        ".".fmt(f)?;
582                    }
583                    key.fmt(f)?;
584                }
585            }
586        }
587        Ok(())
588    }
589}
590
591impl Default for HighlightMap {
592    fn default() -> Self {
593        Self(Arc::new([]))
594    }
595}
596
597impl Default for HighlightId {
598    fn default() -> Self {
599        DEFAULT_HIGHLIGHT_ID
600    }
601}
602
603fn deep_merge_json(base: &mut Map<String, Value>, extension: Map<String, Value>) {
604    for (key, extension_value) in extension {
605        if let Value::Object(extension_object) = extension_value {
606            if let Some(base_object) = base.get_mut(&key).and_then(|value| value.as_object_mut()) {
607                deep_merge_json(base_object, extension_object);
608            } else {
609                base.insert(key, Value::Object(extension_object));
610            }
611        } else {
612            base.insert(key, extension_value);
613        }
614    }
615}
616
617fn find_references(value: &Value, key_path: &mut KeyPath, references: &mut KeyPathReferenceSet) {
618    match value {
619        Value::Array(vec) => {
620            for (ix, value) in vec.iter().enumerate() {
621                key_path.0.push(Key::Array(ix));
622                find_references(value, key_path, references);
623                key_path.0.pop();
624            }
625        }
626        Value::Object(map) => {
627            for (key, value) in map.iter() {
628                if key == "extends" {
629                    if let Some(source_path) = value.as_str().and_then(|s| s.strip_prefix("$")) {
630                        references.insert(KeyPathReference {
631                            source: KeyPath::new(source_path),
632                            target: key_path.clone(),
633                        });
634                    }
635                } else {
636                    key_path.0.push(Key::Object(key.to_string()));
637                    find_references(value, key_path, references);
638                    key_path.0.pop();
639                }
640            }
641        }
642        Value::String(string) => {
643            if let Some(source_path) = string.strip_prefix("$") {
644                references.insert(KeyPathReference {
645                    source: KeyPath::new(source_path),
646                    target: key_path.clone(),
647                });
648            }
649        }
650        _ => {}
651    }
652}
653
654fn value_at<'a>(object: &'a mut Map<String, Value>, key_path: &KeyPath) -> Option<&'a mut Value> {
655    let mut key_path = key_path.0.iter();
656    if let Some(Key::Object(first_key)) = key_path.next() {
657        let mut cur_value = object.get_mut(first_key);
658        for key in key_path {
659            if let Some(value) = cur_value {
660                match key {
661                    Key::Array(ix) => cur_value = value.get_mut(ix),
662                    Key::Object(key) => cur_value = value.get_mut(key),
663                }
664            } else {
665                return None;
666            }
667        }
668        cur_value
669    } else {
670        None
671    }
672}
673
674pub fn deserialize_syntax_theme<'de, D>(
675    deserializer: D,
676) -> Result<Vec<(String, TextStyle)>, D::Error>
677where
678    D: Deserializer<'de>,
679{
680    let mut result = Vec::<(String, TextStyle)>::new();
681
682    let syntax_data: HashMap<String, TextStyle> = Deserialize::deserialize(deserializer)?;
683    for (key, style) in syntax_data {
684        match result.binary_search_by(|(needle, _)| needle.cmp(&key)) {
685            Ok(i) | Err(i) => {
686                result.insert(i, (key, style));
687            }
688        }
689    }
690
691    Ok(result)
692}
693
694#[cfg(test)]
695mod tests {
696    use rand::{prelude::StdRng, Rng};
697
698    use super::*;
699    use crate::assets::Assets;
700
701    #[test]
702    fn test_bundled_themes() {
703        let registry = ThemeRegistry::new(Assets);
704        let mut has_default_theme = false;
705        for theme_name in registry.list() {
706            let theme = registry.get(&theme_name).unwrap();
707            if theme.name == DEFAULT_THEME_NAME {
708                has_default_theme = true;
709            }
710            assert_eq!(theme.name, theme_name);
711        }
712        assert!(has_default_theme);
713    }
714
715    #[test]
716    fn test_theme_extension() {
717        let assets = TestAssets(&[
718            (
719                "themes/_base.toml",
720                r##"
721                [ui.active_tab]
722                extends = "$ui.tab"
723                border.color = "#666666"
724                text = "$text_colors.bright"
725
726                [ui.tab]
727                extends = "$ui.element"
728                text = "$text_colors.dull"
729
730                [ui.element]
731                background = "#111111"
732                border = {width = 2.0, color = "#00000000"}
733
734                [editor]
735                background = "#222222"
736                default_text = "$text_colors.regular"
737                "##,
738            ),
739            (
740                "themes/light.toml",
741                r##"
742                extends = "_base"
743
744                [text_colors]
745                bright = "#ffffff"
746                regular = "#eeeeee"
747                dull = "#dddddd"
748
749                [editor]
750                background = "#232323"
751                "##,
752            ),
753        ]);
754
755        let registry = ThemeRegistry::new(assets);
756        let theme_data = registry.load("light", true).unwrap();
757        assert_eq!(
758            theme_data.as_ref(),
759            &serde_json::json!({
760              "ui": {
761                "active_tab": {
762                  "background": "#111111",
763                  "border": {
764                    "width": 2.0,
765                    "color": "#666666"
766                  },
767                  "extends": "$ui.tab",
768                  "text": "#ffffff"
769                },
770                "tab": {
771                  "background": "#111111",
772                  "border": {
773                    "width": 2.0,
774                    "color": "#00000000"
775                  },
776                  "extends": "$ui.element",
777                  "text": "#dddddd"
778                },
779                "element": {
780                  "background": "#111111",
781                  "border": {
782                    "width": 2.0,
783                    "color": "#00000000"
784                  }
785                }
786              },
787              "editor": {
788                "background": "#232323",
789                "default_text": "#eeeeee"
790              },
791              "extends": "_base",
792              "text_colors": {
793                "bright": "#ffffff",
794                "regular": "#eeeeee",
795                "dull": "#dddddd"
796              }
797            })
798        );
799    }
800
801    #[test]
802    fn test_highlight_map() {
803        let theme = Theme {
804            name: "test".into(),
805            ui: Default::default(),
806            editor: Default::default(),
807            syntax: [
808                ("function", Color::from_u32(0x100000ff)),
809                ("function.method", Color::from_u32(0x200000ff)),
810                ("function.async", Color::from_u32(0x300000ff)),
811                ("variable.builtin.self.rust", Color::from_u32(0x400000ff)),
812                ("variable.builtin", Color::from_u32(0x500000ff)),
813                ("variable", Color::from_u32(0x600000ff)),
814            ]
815            .iter()
816            .map(|(name, color)| (name.to_string(), (*color).into()))
817            .collect(),
818        };
819
820        let capture_names = &[
821            "function.special".to_string(),
822            "function.async.rust".to_string(),
823            "variable.builtin.self".to_string(),
824        ];
825
826        let map = HighlightMap::new(capture_names, &theme);
827        assert_eq!(theme.highlight_name(map.get(0)), Some("function"));
828        assert_eq!(theme.highlight_name(map.get(1)), Some("function.async"));
829        assert_eq!(theme.highlight_name(map.get(2)), Some("variable.builtin"));
830    }
831
832    #[test]
833    fn test_key_path_reference_set_simple() {
834        let input_references = build_refs(&[
835            ("r", "a"),
836            ("a.b.c", "d"),
837            ("d.e", "f"),
838            ("t.u", "v"),
839            ("v.w", "x"),
840            ("v.y", "x"),
841            ("d.h", "i"),
842            ("v.z", "x"),
843            ("f.g", "d.h"),
844        ]);
845        let expected_references = build_refs(&[
846            ("d.h", "i"),
847            ("f.g", "d.h"),
848            ("d.e", "f"),
849            ("a.b.c", "d"),
850            ("r", "a"),
851            ("v.w", "x"),
852            ("v.y", "x"),
853            ("v.z", "x"),
854            ("t.u", "v"),
855        ])
856        .collect::<Vec<_>>();
857
858        let mut reference_set = KeyPathReferenceSet::default();
859        for reference in input_references {
860            reference_set.insert(reference);
861        }
862        assert_eq!(reference_set.top_sort().unwrap(), expected_references);
863    }
864
865    #[test]
866    fn test_key_path_reference_set_with_cycles() {
867        let input_references = build_refs(&[
868            ("x", "a.b"),
869            ("y", "x.c"),
870            ("a.b.c", "d.e"),
871            ("d.e.f", "g.h"),
872            ("g.h.i", "a"),
873        ]);
874
875        let mut reference_set = KeyPathReferenceSet::default();
876        for reference in input_references {
877            reference_set.insert(reference);
878        }
879
880        assert_eq!(
881            reference_set.top_sort().unwrap_err(),
882            &[
883                KeyPath::new("a"),
884                KeyPath::new("a.b.c"),
885                KeyPath::new("d.e"),
886                KeyPath::new("d.e.f"),
887                KeyPath::new("g.h"),
888                KeyPath::new("g.h.i"),
889            ]
890        );
891    }
892
893    #[gpui::test(iterations = 20)]
894    async fn test_key_path_reference_set_random(mut rng: StdRng) {
895        let examples: &[&[_]] = &[
896            &[
897                ("n.d.h", "i"),
898                ("f.g", "n.d.h"),
899                ("n.d.e", "f"),
900                ("a.b.c", "n.d"),
901                ("r", "a"),
902                ("v.w", "x"),
903                ("v.y", "x"),
904                ("v.z", "x"),
905                ("t.u", "v"),
906            ],
907            &[
908                ("w.x.y.z", "t.u.z"),
909                ("x", "w.x"),
910                ("a.b.c1", "x.b1.c"),
911                ("a.b.c2", "x.b2.c"),
912            ],
913            &[
914                ("x.y", "m.n.n.o.q"),
915                ("x.y.z", "m.n.n.o.p"),
916                ("u.v.w", "x.y.z"),
917                ("a.b.c.d", "u.v"),
918                ("a.b.c.d.e", "u.v"),
919                ("a.b.c.d.f", "u.v"),
920                ("a.b.c.d.g", "u.v"),
921            ],
922        ];
923
924        for example in examples {
925            let expected_references = build_refs(example).collect::<Vec<_>>();
926            let mut input_references = expected_references.clone();
927            input_references.sort_by_key(|_| rng.gen_range(0..1000));
928            let mut reference_set = KeyPathReferenceSet::default();
929            for reference in input_references {
930                reference_set.insert(reference);
931            }
932            assert_eq!(reference_set.top_sort().unwrap(), expected_references);
933        }
934    }
935
936    fn build_refs<'a>(rows: &'a [(&str, &str)]) -> impl Iterator<Item = KeyPathReference> + 'a {
937        rows.iter().map(|(target, source)| KeyPathReference {
938            target: KeyPath::new(target),
939            source: KeyPath::new(source),
940        })
941    }
942
943    struct TestAssets(&'static [(&'static str, &'static str)]);
944
945    impl AssetSource for TestAssets {
946        fn load(&self, path: &str) -> Result<std::borrow::Cow<[u8]>> {
947            if let Some(row) = self.0.iter().find(|e| e.0 == path) {
948                Ok(row.1.as_bytes().into())
949            } else {
950                Err(anyhow!("no such path {}", path))
951            }
952        }
953
954        fn list(&self, prefix: &str) -> Vec<std::borrow::Cow<'static, str>> {
955            self.0
956                .iter()
957                .copied()
958                .filter_map(|(path, _)| {
959                    if path.starts_with(prefix) {
960                        Some(path.into())
961                    } else {
962                        None
963                    }
964                })
965                .collect()
966        }
967    }
968}