1use std::sync::Arc;
2
3use gpui::{HighlightStyle, Hsla};
4
5use crate::{
6 blue, cyan, gold, indigo, iris, jade, lime, mint, neutral, orange, plum, purple, red, sky,
7 tomato, yellow,
8};
9
10#[derive(Debug, PartialEq, Eq, Clone, Default)]
11pub struct SyntaxTheme {
12 pub highlights: Vec<(String, HighlightStyle)>,
13}
14
15impl SyntaxTheme {
16 pub fn light() -> Self {
17 Self {
18 highlights: vec![
19 ("attribute".into(), cyan().light().step_11().into()),
20 ("boolean".into(), tomato().light().step_11().into()),
21 ("comment".into(), neutral().light().step_10().into()),
22 ("comment.doc".into(), iris().light().step_11().into()),
23 ("constant".into(), red().light().step_9().into()),
24 ("constructor".into(), red().light().step_9().into()),
25 ("embedded".into(), red().light().step_9().into()),
26 ("emphasis".into(), red().light().step_9().into()),
27 ("emphasis.strong".into(), red().light().step_9().into()),
28 ("enum".into(), red().light().step_9().into()),
29 ("function".into(), red().light().step_9().into()),
30 ("hint".into(), red().light().step_9().into()),
31 ("keyword".into(), orange().light().step_9().into()),
32 ("label".into(), red().light().step_9().into()),
33 ("link_text".into(), red().light().step_9().into()),
34 ("link_uri".into(), red().light().step_9().into()),
35 ("number".into(), purple().light().step_10().into()),
36 ("operator".into(), red().light().step_9().into()),
37 ("predictive".into(), red().light().step_9().into()),
38 ("preproc".into(), red().light().step_9().into()),
39 ("primary".into(), red().light().step_9().into()),
40 ("property".into(), red().light().step_9().into()),
41 ("punctuation".into(), neutral().light().step_11().into()),
42 (
43 "punctuation.bracket".into(),
44 neutral().light().step_11().into(),
45 ),
46 (
47 "punctuation.delimiter".into(),
48 neutral().light().step_10().into(),
49 ),
50 (
51 "punctuation.list_marker".into(),
52 blue().light().step_11().into(),
53 ),
54 ("punctuation.special".into(), red().light().step_9().into()),
55 ("string".into(), jade().light().step_9().into()),
56 ("string.escape".into(), red().light().step_9().into()),
57 ("string.regex".into(), tomato().light().step_9().into()),
58 ("string.special".into(), red().light().step_9().into()),
59 (
60 "string.special.symbol".into(),
61 red().light().step_9().into(),
62 ),
63 ("tag".into(), red().light().step_9().into()),
64 ("text.literal".into(), red().light().step_9().into()),
65 ("title".into(), red().light().step_9().into()),
66 ("type".into(), cyan().light().step_9().into()),
67 ("variable".into(), red().light().step_9().into()),
68 ("variable.special".into(), red().light().step_9().into()),
69 ("variant".into(), red().light().step_9().into()),
70 ],
71 }
72 }
73
74 pub fn dark() -> Self {
75 Self {
76 highlights: vec![
77 ("attribute".into(), tomato().dark().step_11().into()),
78 ("boolean".into(), tomato().dark().step_11().into()),
79 ("comment".into(), neutral().dark().step_11().into()),
80 ("comment.doc".into(), iris().dark().step_12().into()),
81 ("constant".into(), orange().dark().step_11().into()),
82 ("constructor".into(), gold().dark().step_11().into()),
83 ("embedded".into(), red().dark().step_11().into()),
84 ("emphasis".into(), red().dark().step_11().into()),
85 ("emphasis.strong".into(), red().dark().step_11().into()),
86 ("enum".into(), yellow().dark().step_11().into()),
87 ("function".into(), blue().dark().step_11().into()),
88 ("hint".into(), indigo().dark().step_11().into()),
89 ("keyword".into(), plum().dark().step_11().into()),
90 ("label".into(), red().dark().step_11().into()),
91 ("link_text".into(), red().dark().step_11().into()),
92 ("link_uri".into(), red().dark().step_11().into()),
93 ("number".into(), red().dark().step_11().into()),
94 ("operator".into(), red().dark().step_11().into()),
95 ("predictive".into(), red().dark().step_11().into()),
96 ("preproc".into(), red().dark().step_11().into()),
97 ("primary".into(), red().dark().step_11().into()),
98 ("property".into(), red().dark().step_11().into()),
99 ("punctuation".into(), neutral().dark().step_11().into()),
100 (
101 "punctuation.bracket".into(),
102 neutral().dark().step_11().into(),
103 ),
104 (
105 "punctuation.delimiter".into(),
106 neutral().dark().step_11().into(),
107 ),
108 (
109 "punctuation.list_marker".into(),
110 blue().dark().step_11().into(),
111 ),
112 ("punctuation.special".into(), red().dark().step_11().into()),
113 ("string".into(), lime().dark().step_11().into()),
114 ("string.escape".into(), orange().dark().step_11().into()),
115 ("string.regex".into(), tomato().dark().step_11().into()),
116 ("string.special".into(), red().dark().step_11().into()),
117 (
118 "string.special.symbol".into(),
119 red().dark().step_11().into(),
120 ),
121 ("tag".into(), red().dark().step_11().into()),
122 ("text.literal".into(), purple().dark().step_11().into()),
123 ("title".into(), sky().dark().step_11().into()),
124 ("type".into(), mint().dark().step_11().into()),
125 ("variable".into(), red().dark().step_11().into()),
126 ("variable.special".into(), red().dark().step_11().into()),
127 ("variant".into(), red().dark().step_11().into()),
128 ],
129 }
130 }
131
132 #[cfg(any(test, feature = "test-support"))]
133 pub fn new_test(colors: impl IntoIterator<Item = (&'static str, Hsla)>) -> Self {
134 Self::new_test_styles(colors.into_iter().map(|(key, color)| {
135 (
136 key,
137 HighlightStyle {
138 color: Some(color),
139 ..Default::default()
140 },
141 )
142 }))
143 }
144
145 #[cfg(any(test, feature = "test-support"))]
146 pub fn new_test_styles(
147 colors: impl IntoIterator<Item = (&'static str, HighlightStyle)>,
148 ) -> Self {
149 Self {
150 highlights: colors
151 .into_iter()
152 .map(|(key, style)| (key.to_owned(), style))
153 .collect(),
154 }
155 }
156
157 pub fn get(&self, name: &str) -> HighlightStyle {
158 self.highlights
159 .iter()
160 .find_map(|entry| if entry.0 == name { Some(entry.1) } else { None })
161 .unwrap_or_default()
162 }
163
164 pub fn color(&self, name: &str) -> Hsla {
165 self.get(name).color.unwrap_or_default()
166 }
167
168 /// Returns a new [`Arc<SyntaxTheme>`] with the given syntax styles merged in.
169 pub fn merge(base: Arc<Self>, user_syntax_styles: Vec<(String, HighlightStyle)>) -> Arc<Self> {
170 if user_syntax_styles.is_empty() {
171 return base;
172 }
173
174 let mut merged_highlights = base.highlights.clone();
175
176 for (name, highlight) in user_syntax_styles {
177 if let Some((_, existing_highlight)) = merged_highlights
178 .iter_mut()
179 .find(|(existing_name, _)| existing_name == &name)
180 {
181 existing_highlight.color = highlight.color.or(existing_highlight.color);
182 existing_highlight.font_weight =
183 highlight.font_weight.or(existing_highlight.font_weight);
184 existing_highlight.font_style =
185 highlight.font_style.or(existing_highlight.font_style);
186 existing_highlight.background_color = highlight
187 .background_color
188 .or(existing_highlight.background_color);
189 existing_highlight.underline = highlight.underline.or(existing_highlight.underline);
190 existing_highlight.strikethrough =
191 highlight.strikethrough.or(existing_highlight.strikethrough);
192 existing_highlight.fade_out = highlight.fade_out.or(existing_highlight.fade_out);
193 } else {
194 merged_highlights.push((name, highlight));
195 }
196 }
197
198 Arc::new(Self {
199 highlights: merged_highlights,
200 })
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use gpui::FontStyle;
207
208 use super::*;
209
210 #[test]
211 fn test_syntax_theme_merge() {
212 // Merging into an empty `SyntaxTheme` keeps all the user-defined styles.
213 let syntax_theme = SyntaxTheme::merge(
214 Arc::new(SyntaxTheme::new_test([])),
215 vec![
216 (
217 "foo".to_string(),
218 HighlightStyle {
219 color: Some(gpui::red()),
220 ..Default::default()
221 },
222 ),
223 (
224 "foo.bar".to_string(),
225 HighlightStyle {
226 color: Some(gpui::green()),
227 ..Default::default()
228 },
229 ),
230 ],
231 );
232 assert_eq!(
233 syntax_theme,
234 Arc::new(SyntaxTheme::new_test([
235 ("foo", gpui::red()),
236 ("foo.bar", gpui::green())
237 ]))
238 );
239
240 // Merging empty user-defined styles keeps all the base styles.
241 let syntax_theme = SyntaxTheme::merge(
242 Arc::new(SyntaxTheme::new_test([
243 ("foo", gpui::blue()),
244 ("foo.bar", gpui::red()),
245 ])),
246 Vec::new(),
247 );
248 assert_eq!(
249 syntax_theme,
250 Arc::new(SyntaxTheme::new_test([
251 ("foo", gpui::blue()),
252 ("foo.bar", gpui::red())
253 ]))
254 );
255
256 let syntax_theme = SyntaxTheme::merge(
257 Arc::new(SyntaxTheme::new_test([
258 ("foo", gpui::red()),
259 ("foo.bar", gpui::green()),
260 ])),
261 vec![(
262 "foo.bar".to_string(),
263 HighlightStyle {
264 color: Some(gpui::yellow()),
265 ..Default::default()
266 },
267 )],
268 );
269 assert_eq!(
270 syntax_theme,
271 Arc::new(SyntaxTheme::new_test([
272 ("foo", gpui::red()),
273 ("foo.bar", gpui::yellow())
274 ]))
275 );
276
277 let syntax_theme = SyntaxTheme::merge(
278 Arc::new(SyntaxTheme::new_test([
279 ("foo", gpui::red()),
280 ("foo.bar", gpui::green()),
281 ])),
282 vec![(
283 "foo.bar".to_string(),
284 HighlightStyle {
285 font_style: Some(FontStyle::Italic),
286 ..Default::default()
287 },
288 )],
289 );
290 assert_eq!(
291 syntax_theme,
292 Arc::new(SyntaxTheme::new_test_styles([
293 (
294 "foo",
295 HighlightStyle {
296 color: Some(gpui::red()),
297 ..Default::default()
298 }
299 ),
300 (
301 "foo.bar",
302 HighlightStyle {
303 color: Some(gpui::green()),
304 font_style: Some(FontStyle::Italic),
305 ..Default::default()
306 }
307 )
308 ]))
309 );
310 }
311}