syntax.rs

  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}