1use std::sync::Arc;
2
3use gpui::{HighlightStyle, Hsla};
4
5#[derive(Debug, PartialEq, Eq, Clone, Default)]
6pub struct SyntaxTheme {
7 pub highlights: Vec<(String, HighlightStyle)>,
8}
9
10impl SyntaxTheme {
11 #[cfg(any(test, feature = "test-support"))]
12 pub fn new_test(colors: impl IntoIterator<Item = (&'static str, Hsla)>) -> Self {
13 Self::new_test_styles(colors.into_iter().map(|(key, color)| {
14 (
15 key,
16 HighlightStyle {
17 color: Some(color),
18 ..Default::default()
19 },
20 )
21 }))
22 }
23
24 #[cfg(any(test, feature = "test-support"))]
25 pub fn new_test_styles(
26 colors: impl IntoIterator<Item = (&'static str, HighlightStyle)>,
27 ) -> Self {
28 Self {
29 highlights: colors
30 .into_iter()
31 .map(|(key, style)| (key.to_owned(), style))
32 .collect(),
33 }
34 }
35
36 pub fn get(&self, name: &str) -> HighlightStyle {
37 self.highlights
38 .iter()
39 .find_map(|entry| if entry.0 == name { Some(entry.1) } else { None })
40 .unwrap_or_default()
41 }
42
43 pub fn color(&self, name: &str) -> Hsla {
44 self.get(name).color.unwrap_or_default()
45 }
46
47 /// Returns a new [`Arc<SyntaxTheme>`] with the given syntax styles merged in.
48 pub fn merge(base: Arc<Self>, user_syntax_styles: Vec<(String, HighlightStyle)>) -> Arc<Self> {
49 if user_syntax_styles.is_empty() {
50 return base;
51 }
52
53 let mut merged_highlights = base.highlights.clone();
54
55 for (name, highlight) in user_syntax_styles {
56 if let Some((_, existing_highlight)) = merged_highlights
57 .iter_mut()
58 .find(|(existing_name, _)| existing_name == &name)
59 {
60 existing_highlight.color = highlight.color.or(existing_highlight.color);
61 existing_highlight.font_weight =
62 highlight.font_weight.or(existing_highlight.font_weight);
63 existing_highlight.font_style =
64 highlight.font_style.or(existing_highlight.font_style);
65 existing_highlight.background_color = highlight
66 .background_color
67 .or(existing_highlight.background_color);
68 existing_highlight.underline = highlight.underline.or(existing_highlight.underline);
69 existing_highlight.strikethrough =
70 highlight.strikethrough.or(existing_highlight.strikethrough);
71 existing_highlight.fade_out = highlight.fade_out.or(existing_highlight.fade_out);
72 } else {
73 merged_highlights.push((name, highlight));
74 }
75 }
76
77 Arc::new(Self {
78 highlights: merged_highlights,
79 })
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use gpui::FontStyle;
86
87 use super::*;
88
89 #[test]
90 fn test_syntax_theme_merge() {
91 // Merging into an empty `SyntaxTheme` keeps all the user-defined styles.
92 let syntax_theme = SyntaxTheme::merge(
93 Arc::new(SyntaxTheme::new_test([])),
94 vec![
95 (
96 "foo".to_string(),
97 HighlightStyle {
98 color: Some(gpui::red()),
99 ..Default::default()
100 },
101 ),
102 (
103 "foo.bar".to_string(),
104 HighlightStyle {
105 color: Some(gpui::green()),
106 ..Default::default()
107 },
108 ),
109 ],
110 );
111 assert_eq!(
112 syntax_theme,
113 Arc::new(SyntaxTheme::new_test([
114 ("foo", gpui::red()),
115 ("foo.bar", gpui::green())
116 ]))
117 );
118
119 // Merging empty user-defined styles keeps all the base styles.
120 let syntax_theme = SyntaxTheme::merge(
121 Arc::new(SyntaxTheme::new_test([
122 ("foo", gpui::blue()),
123 ("foo.bar", gpui::red()),
124 ])),
125 Vec::new(),
126 );
127 assert_eq!(
128 syntax_theme,
129 Arc::new(SyntaxTheme::new_test([
130 ("foo", gpui::blue()),
131 ("foo.bar", gpui::red())
132 ]))
133 );
134
135 let syntax_theme = SyntaxTheme::merge(
136 Arc::new(SyntaxTheme::new_test([
137 ("foo", gpui::red()),
138 ("foo.bar", gpui::green()),
139 ])),
140 vec![(
141 "foo.bar".to_string(),
142 HighlightStyle {
143 color: Some(gpui::yellow()),
144 ..Default::default()
145 },
146 )],
147 );
148 assert_eq!(
149 syntax_theme,
150 Arc::new(SyntaxTheme::new_test([
151 ("foo", gpui::red()),
152 ("foo.bar", gpui::yellow())
153 ]))
154 );
155
156 let syntax_theme = SyntaxTheme::merge(
157 Arc::new(SyntaxTheme::new_test([
158 ("foo", gpui::red()),
159 ("foo.bar", gpui::green()),
160 ])),
161 vec![(
162 "foo.bar".to_string(),
163 HighlightStyle {
164 font_style: Some(FontStyle::Italic),
165 ..Default::default()
166 },
167 )],
168 );
169 assert_eq!(
170 syntax_theme,
171 Arc::new(SyntaxTheme::new_test_styles([
172 (
173 "foo",
174 HighlightStyle {
175 color: Some(gpui::red()),
176 ..Default::default()
177 }
178 ),
179 (
180 "foo.bar",
181 HighlightStyle {
182 color: Some(gpui::green()),
183 font_style: Some(FontStyle::Italic),
184 ..Default::default()
185 }
186 )
187 ]))
188 );
189 }
190}