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    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}