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 pub fn highlight_id(&self, name: &str) -> Option<u32> {
48 let ix = self.highlights.iter().position(|entry| entry.0 == name)?;
49 Some(ix as u32)
50 }
51
52 /// Returns a new [`Arc<SyntaxTheme>`] with the given syntax styles merged in.
53 pub fn merge(base: Arc<Self>, user_syntax_styles: Vec<(String, HighlightStyle)>) -> Arc<Self> {
54 if user_syntax_styles.is_empty() {
55 return base;
56 }
57
58 let mut merged_highlights = base.highlights.clone();
59
60 for (name, highlight) in user_syntax_styles {
61 if let Some((_, existing_highlight)) = merged_highlights
62 .iter_mut()
63 .find(|(existing_name, _)| existing_name == &name)
64 {
65 existing_highlight.color = highlight.color.or(existing_highlight.color);
66 existing_highlight.font_weight =
67 highlight.font_weight.or(existing_highlight.font_weight);
68 existing_highlight.font_style =
69 highlight.font_style.or(existing_highlight.font_style);
70 existing_highlight.background_color = highlight
71 .background_color
72 .or(existing_highlight.background_color);
73 existing_highlight.underline = highlight.underline.or(existing_highlight.underline);
74 existing_highlight.strikethrough =
75 highlight.strikethrough.or(existing_highlight.strikethrough);
76 existing_highlight.fade_out = highlight.fade_out.or(existing_highlight.fade_out);
77 } else {
78 merged_highlights.push((name, highlight));
79 }
80 }
81
82 Arc::new(Self {
83 highlights: merged_highlights,
84 })
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use gpui::FontStyle;
91
92 use super::*;
93
94 #[test]
95 fn test_syntax_theme_merge() {
96 // Merging into an empty `SyntaxTheme` keeps all the user-defined styles.
97 let syntax_theme = SyntaxTheme::merge(
98 Arc::new(SyntaxTheme::new_test([])),
99 vec![
100 (
101 "foo".to_string(),
102 HighlightStyle {
103 color: Some(gpui::red()),
104 ..Default::default()
105 },
106 ),
107 (
108 "foo.bar".to_string(),
109 HighlightStyle {
110 color: Some(gpui::green()),
111 ..Default::default()
112 },
113 ),
114 ],
115 );
116 assert_eq!(
117 syntax_theme,
118 Arc::new(SyntaxTheme::new_test([
119 ("foo", gpui::red()),
120 ("foo.bar", gpui::green())
121 ]))
122 );
123
124 // Merging empty user-defined styles keeps all the base styles.
125 let syntax_theme = SyntaxTheme::merge(
126 Arc::new(SyntaxTheme::new_test([
127 ("foo", gpui::blue()),
128 ("foo.bar", gpui::red()),
129 ])),
130 Vec::new(),
131 );
132 assert_eq!(
133 syntax_theme,
134 Arc::new(SyntaxTheme::new_test([
135 ("foo", gpui::blue()),
136 ("foo.bar", gpui::red())
137 ]))
138 );
139
140 let syntax_theme = SyntaxTheme::merge(
141 Arc::new(SyntaxTheme::new_test([
142 ("foo", gpui::red()),
143 ("foo.bar", gpui::green()),
144 ])),
145 vec![(
146 "foo.bar".to_string(),
147 HighlightStyle {
148 color: Some(gpui::yellow()),
149 ..Default::default()
150 },
151 )],
152 );
153 assert_eq!(
154 syntax_theme,
155 Arc::new(SyntaxTheme::new_test([
156 ("foo", gpui::red()),
157 ("foo.bar", gpui::yellow())
158 ]))
159 );
160
161 let syntax_theme = SyntaxTheme::merge(
162 Arc::new(SyntaxTheme::new_test([
163 ("foo", gpui::red()),
164 ("foo.bar", gpui::green()),
165 ])),
166 vec![(
167 "foo.bar".to_string(),
168 HighlightStyle {
169 font_style: Some(FontStyle::Italic),
170 ..Default::default()
171 },
172 )],
173 );
174 assert_eq!(
175 syntax_theme,
176 Arc::new(SyntaxTheme::new_test_styles([
177 (
178 "foo",
179 HighlightStyle {
180 color: Some(gpui::red()),
181 ..Default::default()
182 }
183 ),
184 (
185 "foo.bar",
186 HighlightStyle {
187 color: Some(gpui::green()),
188 font_style: Some(FontStyle::Italic),
189 ..Default::default()
190 }
191 )
192 ]))
193 );
194 }
195}