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}