syntax.rs

  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}