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