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}