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}