syntax.rs

  1#![allow(missing_docs)]
  2
  3use std::sync::Arc;
  4
  5use gpui::{HighlightStyle, Hsla};
  6
  7#[derive(Debug, PartialEq, Eq, Clone, Default)]
  8pub struct SyntaxTheme {
  9    pub highlights: Vec<(String, HighlightStyle)>,
 10}
 11
 12impl SyntaxTheme {
 13    #[cfg(any(test, feature = "test-support"))]
 14    pub fn new_test(colors: impl IntoIterator<Item = (&'static str, Hsla)>) -> Self {
 15        Self::new_test_styles(colors.into_iter().map(|(key, color)| {
 16            (
 17                key,
 18                HighlightStyle {
 19                    color: Some(color),
 20                    ..Default::default()
 21                },
 22            )
 23        }))
 24    }
 25
 26    #[cfg(any(test, feature = "test-support"))]
 27    pub fn new_test_styles(
 28        colors: impl IntoIterator<Item = (&'static str, HighlightStyle)>,
 29    ) -> Self {
 30        Self {
 31            highlights: colors
 32                .into_iter()
 33                .map(|(key, style)| (key.to_owned(), style))
 34                .collect(),
 35        }
 36    }
 37
 38    pub fn get(&self, name: &str) -> HighlightStyle {
 39        self.highlights
 40            .iter()
 41            .find_map(|entry| if entry.0 == name { Some(entry.1) } else { None })
 42            .unwrap_or_default()
 43    }
 44
 45    pub fn color(&self, name: &str) -> Hsla {
 46        self.get(name).color.unwrap_or_default()
 47    }
 48
 49    pub fn highlight_id(&self, name: &str) -> Option<u32> {
 50        let ix = self.highlights.iter().position(|entry| entry.0 == name)?;
 51        Some(ix as u32)
 52    }
 53
 54    /// Returns a new [`Arc<SyntaxTheme>`] with the given syntax styles merged in.
 55    pub fn merge(base: Arc<Self>, user_syntax_styles: Vec<(String, HighlightStyle)>) -> Arc<Self> {
 56        if user_syntax_styles.is_empty() {
 57            return base;
 58        }
 59
 60        let mut merged_highlights = base.highlights.clone();
 61
 62        for (name, highlight) in user_syntax_styles {
 63            if let Some((_, existing_highlight)) = merged_highlights
 64                .iter_mut()
 65                .find(|(existing_name, _)| existing_name == &name)
 66            {
 67                existing_highlight.color = highlight.color.or(existing_highlight.color);
 68                existing_highlight.font_weight =
 69                    highlight.font_weight.or(existing_highlight.font_weight);
 70                existing_highlight.font_style =
 71                    highlight.font_style.or(existing_highlight.font_style);
 72                existing_highlight.background_color = highlight
 73                    .background_color
 74                    .or(existing_highlight.background_color);
 75                existing_highlight.underline = highlight.underline.or(existing_highlight.underline);
 76                existing_highlight.strikethrough =
 77                    highlight.strikethrough.or(existing_highlight.strikethrough);
 78                existing_highlight.fade_out = highlight.fade_out.or(existing_highlight.fade_out);
 79            } else {
 80                merged_highlights.push((name, highlight));
 81            }
 82        }
 83
 84        Arc::new(Self {
 85            highlights: merged_highlights,
 86        })
 87    }
 88}
 89
 90#[cfg(test)]
 91mod tests {
 92    use gpui::FontStyle;
 93
 94    use super::*;
 95
 96    #[test]
 97    fn test_syntax_theme_merge() {
 98        // Merging into an empty `SyntaxTheme` keeps all the user-defined styles.
 99        let syntax_theme = SyntaxTheme::merge(
100            Arc::new(SyntaxTheme::new_test([])),
101            vec![
102                (
103                    "foo".to_string(),
104                    HighlightStyle {
105                        color: Some(gpui::red()),
106                        ..Default::default()
107                    },
108                ),
109                (
110                    "foo.bar".to_string(),
111                    HighlightStyle {
112                        color: Some(gpui::green()),
113                        ..Default::default()
114                    },
115                ),
116            ],
117        );
118        assert_eq!(
119            syntax_theme,
120            Arc::new(SyntaxTheme::new_test([
121                ("foo", gpui::red()),
122                ("foo.bar", gpui::green())
123            ]))
124        );
125
126        // Merging empty user-defined styles keeps all the base styles.
127        let syntax_theme = SyntaxTheme::merge(
128            Arc::new(SyntaxTheme::new_test([
129                ("foo", gpui::blue()),
130                ("foo.bar", gpui::red()),
131            ])),
132            Vec::new(),
133        );
134        assert_eq!(
135            syntax_theme,
136            Arc::new(SyntaxTheme::new_test([
137                ("foo", gpui::blue()),
138                ("foo.bar", gpui::red())
139            ]))
140        );
141
142        let syntax_theme = SyntaxTheme::merge(
143            Arc::new(SyntaxTheme::new_test([
144                ("foo", gpui::red()),
145                ("foo.bar", gpui::green()),
146            ])),
147            vec![(
148                "foo.bar".to_string(),
149                HighlightStyle {
150                    color: Some(gpui::yellow()),
151                    ..Default::default()
152                },
153            )],
154        );
155        assert_eq!(
156            syntax_theme,
157            Arc::new(SyntaxTheme::new_test([
158                ("foo", gpui::red()),
159                ("foo.bar", gpui::yellow())
160            ]))
161        );
162
163        let syntax_theme = SyntaxTheme::merge(
164            Arc::new(SyntaxTheme::new_test([
165                ("foo", gpui::red()),
166                ("foo.bar", gpui::green()),
167            ])),
168            vec![(
169                "foo.bar".to_string(),
170                HighlightStyle {
171                    font_style: Some(FontStyle::Italic),
172                    ..Default::default()
173                },
174            )],
175        );
176        assert_eq!(
177            syntax_theme,
178            Arc::new(SyntaxTheme::new_test_styles([
179                (
180                    "foo",
181                    HighlightStyle {
182                        color: Some(gpui::red()),
183                        ..Default::default()
184                    }
185                ),
186                (
187                    "foo.bar",
188                    HighlightStyle {
189                        color: Some(gpui::green()),
190                        font_style: Some(FontStyle::Italic),
191                        ..Default::default()
192                    }
193                )
194            ]))
195        );
196    }
197}