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 get_opt(&self, name: &str) -> Option<HighlightStyle> {
 46        self.highlights
 47            .iter()
 48            .find_map(|entry| if entry.0 == name { Some(entry.1) } else { None })
 49    }
 50
 51    pub fn color(&self, name: &str) -> Hsla {
 52        self.get(name).color.unwrap_or_default()
 53    }
 54
 55    pub fn highlight_id(&self, name: &str) -> Option<u32> {
 56        let ix = self.highlights.iter().position(|entry| entry.0 == name)?;
 57        Some(ix as u32)
 58    }
 59
 60    /// Returns a new [`Arc<SyntaxTheme>`] with the given syntax styles merged in.
 61    pub fn merge(base: Arc<Self>, user_syntax_styles: Vec<(String, HighlightStyle)>) -> Arc<Self> {
 62        if user_syntax_styles.is_empty() {
 63            return base;
 64        }
 65
 66        let mut merged_highlights = base.highlights.clone();
 67
 68        for (name, highlight) in user_syntax_styles {
 69            if let Some((_, existing_highlight)) = merged_highlights
 70                .iter_mut()
 71                .find(|(existing_name, _)| existing_name == &name)
 72            {
 73                existing_highlight.color = highlight.color.or(existing_highlight.color);
 74                existing_highlight.font_weight =
 75                    highlight.font_weight.or(existing_highlight.font_weight);
 76                existing_highlight.font_style =
 77                    highlight.font_style.or(existing_highlight.font_style);
 78                existing_highlight.background_color = highlight
 79                    .background_color
 80                    .or(existing_highlight.background_color);
 81                existing_highlight.underline = highlight.underline.or(existing_highlight.underline);
 82                existing_highlight.strikethrough =
 83                    highlight.strikethrough.or(existing_highlight.strikethrough);
 84                existing_highlight.fade_out = highlight.fade_out.or(existing_highlight.fade_out);
 85            } else {
 86                merged_highlights.push((name, highlight));
 87            }
 88        }
 89
 90        Arc::new(Self {
 91            highlights: merged_highlights,
 92        })
 93    }
 94}
 95
 96#[cfg(test)]
 97mod tests {
 98    use gpui::FontStyle;
 99
100    use super::*;
101
102    #[test]
103    fn test_syntax_theme_merge() {
104        // Merging into an empty `SyntaxTheme` keeps all the user-defined styles.
105        let syntax_theme = SyntaxTheme::merge(
106            Arc::new(SyntaxTheme::new_test([])),
107            vec![
108                (
109                    "foo".to_string(),
110                    HighlightStyle {
111                        color: Some(gpui::red()),
112                        ..Default::default()
113                    },
114                ),
115                (
116                    "foo.bar".to_string(),
117                    HighlightStyle {
118                        color: Some(gpui::green()),
119                        ..Default::default()
120                    },
121                ),
122            ],
123        );
124        assert_eq!(
125            syntax_theme,
126            Arc::new(SyntaxTheme::new_test([
127                ("foo", gpui::red()),
128                ("foo.bar", gpui::green())
129            ]))
130        );
131
132        // Merging empty user-defined styles keeps all the base styles.
133        let syntax_theme = SyntaxTheme::merge(
134            Arc::new(SyntaxTheme::new_test([
135                ("foo", gpui::blue()),
136                ("foo.bar", gpui::red()),
137            ])),
138            Vec::new(),
139        );
140        assert_eq!(
141            syntax_theme,
142            Arc::new(SyntaxTheme::new_test([
143                ("foo", gpui::blue()),
144                ("foo.bar", gpui::red())
145            ]))
146        );
147
148        let syntax_theme = SyntaxTheme::merge(
149            Arc::new(SyntaxTheme::new_test([
150                ("foo", gpui::red()),
151                ("foo.bar", gpui::green()),
152            ])),
153            vec![(
154                "foo.bar".to_string(),
155                HighlightStyle {
156                    color: Some(gpui::yellow()),
157                    ..Default::default()
158                },
159            )],
160        );
161        assert_eq!(
162            syntax_theme,
163            Arc::new(SyntaxTheme::new_test([
164                ("foo", gpui::red()),
165                ("foo.bar", gpui::yellow())
166            ]))
167        );
168
169        let syntax_theme = SyntaxTheme::merge(
170            Arc::new(SyntaxTheme::new_test([
171                ("foo", gpui::red()),
172                ("foo.bar", gpui::green()),
173            ])),
174            vec![(
175                "foo.bar".to_string(),
176                HighlightStyle {
177                    font_style: Some(FontStyle::Italic),
178                    ..Default::default()
179                },
180            )],
181        );
182        assert_eq!(
183            syntax_theme,
184            Arc::new(SyntaxTheme::new_test_styles([
185                (
186                    "foo",
187                    HighlightStyle {
188                        color: Some(gpui::red()),
189                        ..Default::default()
190                    }
191                ),
192                (
193                    "foo.bar",
194                    HighlightStyle {
195                        color: Some(gpui::green()),
196                        font_style: Some(FontStyle::Italic),
197                        ..Default::default()
198                    }
199                )
200            ]))
201        );
202    }
203}