1use anyhow::Result;
2use gpui::rgba;
3use indexmap::IndexMap;
4use strum::IntoEnumIterator;
5use theme::{
6 StatusColorsRefinement, ThemeColorsRefinement, UserFontStyle, UserFontWeight,
7 UserHighlightStyle, UserSyntaxTheme, UserTheme, UserThemeStylesRefinement,
8};
9
10use crate::color::try_parse_color;
11use crate::util::Traverse;
12use crate::vscode::{VsCodeTheme, VsCodeTokenScope};
13use crate::ThemeMetadata;
14
15use super::ZedSyntaxToken;
16
17pub(crate) fn try_parse_font_weight(font_style: &str) -> Option<UserFontWeight> {
18 match font_style {
19 style if style.contains("bold") => Some(UserFontWeight::BOLD),
20 _ => None,
21 }
22}
23
24pub(crate) fn try_parse_font_style(font_style: &str) -> Option<UserFontStyle> {
25 match font_style {
26 style if style.contains("italic") => Some(UserFontStyle::Italic),
27 style if style.contains("oblique") => Some(UserFontStyle::Oblique),
28 _ => None,
29 }
30}
31
32pub struct VsCodeThemeConverter {
33 theme: VsCodeTheme,
34 theme_metadata: ThemeMetadata,
35 syntax_overrides: IndexMap<String, Vec<String>>,
36}
37
38impl VsCodeThemeConverter {
39 pub fn new(
40 theme: VsCodeTheme,
41 theme_metadata: ThemeMetadata,
42 syntax_overrides: IndexMap<String, Vec<String>>,
43 ) -> Self {
44 Self {
45 theme,
46 theme_metadata,
47 syntax_overrides,
48 }
49 }
50
51 pub fn convert(self) -> Result<UserTheme> {
52 let appearance = self.theme_metadata.appearance.into();
53
54 let status_color_refinements = self.convert_status_colors()?;
55 let theme_colors_refinements = self.convert_theme_colors()?;
56 let syntax_theme = self.convert_syntax_theme()?;
57
58 Ok(UserTheme {
59 name: self.theme_metadata.name.into(),
60 appearance,
61 styles: UserThemeStylesRefinement {
62 colors: theme_colors_refinements,
63 status: status_color_refinements,
64 syntax: Some(syntax_theme),
65 },
66 })
67 }
68
69 fn convert_status_colors(&self) -> Result<StatusColorsRefinement> {
70 let vscode_colors = &self.theme.colors;
71
72 let vscode_base_status_colors = StatusColorsRefinement {
73 hint: Some(rgba(0x969696ff).into()),
74 ..Default::default()
75 };
76
77 Ok(StatusColorsRefinement {
78 // conflict: None,
79 // created: None,
80 deleted: vscode_colors
81 .error_foreground
82 .as_ref()
83 .traverse(|color| try_parse_color(&color))?,
84 error: vscode_colors
85 .error_foreground
86 .as_ref()
87 .traverse(|color| try_parse_color(&color))?,
88 hidden: vscode_colors
89 .tab_inactive_foreground
90 .as_ref()
91 .traverse(|color| try_parse_color(&color))?,
92 hint: vscode_colors
93 .editor_inlay_hint_foreground
94 .as_ref()
95 .traverse(|color| try_parse_color(&color))?
96 .or(vscode_base_status_colors.hint),
97 // ignored: None,
98 // info: None,
99 // modified: None,
100 // renamed: None,
101 // success: None,
102 warning: vscode_colors
103 .list_warning_foreground
104 .as_ref()
105 .traverse(|color| try_parse_color(&color))?,
106 ..Default::default()
107 })
108 }
109
110 fn convert_theme_colors(&self) -> Result<ThemeColorsRefinement> {
111 let vscode_colors = &self.theme.colors;
112
113 Ok(ThemeColorsRefinement {
114 border: vscode_colors
115 .panel_border
116 .as_ref()
117 .traverse(|color| try_parse_color(&color))?,
118 border_variant: vscode_colors
119 .panel_border
120 .as_ref()
121 .traverse(|color| try_parse_color(&color))?,
122 border_focused: vscode_colors
123 .focus_border
124 .as_ref()
125 .traverse(|color| try_parse_color(&color))?,
126 border_disabled: vscode_colors
127 .panel_border
128 .as_ref()
129 .traverse(|color| try_parse_color(&color))?,
130 border_selected: vscode_colors
131 .panel_border
132 .as_ref()
133 .traverse(|color| try_parse_color(&color))?,
134 border_transparent: vscode_colors
135 .panel_border
136 .as_ref()
137 .traverse(|color| try_parse_color(&color))?,
138 elevated_surface_background: vscode_colors
139 .panel_background
140 .as_ref()
141 .traverse(|color| try_parse_color(&color))?,
142 surface_background: vscode_colors
143 .panel_background
144 .as_ref()
145 .traverse(|color| try_parse_color(&color))?,
146 background: vscode_colors
147 .editor_background
148 .as_ref()
149 .traverse(|color| try_parse_color(&color))?,
150 title_bar_background: vscode_colors
151 .title_bar_active_background
152 .as_ref()
153 .traverse(|color| try_parse_color(&color))?,
154 status_bar_background: vscode_colors
155 .status_bar_background
156 .as_ref()
157 .traverse(|color| try_parse_color(&color))?,
158 element_background: vscode_colors
159 .button_background
160 .as_ref()
161 .traverse(|color| try_parse_color(&color))?,
162 element_hover: vscode_colors
163 .list_hover_background
164 .as_ref()
165 .traverse(|color| try_parse_color(&color))?,
166 element_selected: vscode_colors
167 .list_active_selection_background
168 .as_ref()
169 .traverse(|color| try_parse_color(&color))?,
170 ghost_element_hover: vscode_colors
171 .list_hover_background
172 .as_ref()
173 .traverse(|color| try_parse_color(&color))?,
174 drop_target_background: vscode_colors
175 .list_drop_background
176 .as_ref()
177 .traverse(|color| try_parse_color(&color))?,
178 text: vscode_colors
179 .foreground
180 .as_ref()
181 .traverse(|color| try_parse_color(&color))?
182 .or_else(|| {
183 self.theme
184 .token_colors
185 .iter()
186 .find(|token_color| token_color.scope.is_none())
187 .and_then(|token_color| token_color.settings.foreground.as_ref())
188 .traverse(|color| try_parse_color(&color))
189 .ok()
190 .flatten()
191 }),
192 tab_bar_background: vscode_colors
193 .panel_background
194 .as_ref()
195 .traverse(|color| try_parse_color(&color))?,
196 tab_active_background: vscode_colors
197 .tab_active_background
198 .as_ref()
199 .traverse(|color| try_parse_color(&color))?,
200 tab_inactive_background: vscode_colors
201 .tab_inactive_background
202 .as_ref()
203 .traverse(|color| try_parse_color(&color))?,
204 toolbar_background: vscode_colors
205 .panel_background
206 .as_ref()
207 .traverse(|color| try_parse_color(&color))?,
208 editor_background: vscode_colors
209 .editor_background
210 .as_ref()
211 .traverse(|color| try_parse_color(&color))?,
212 editor_gutter_background: vscode_colors
213 .editor_background
214 .as_ref()
215 .traverse(|color| try_parse_color(&color))?,
216 editor_line_number: vscode_colors
217 .editor_line_number_foreground
218 .as_ref()
219 .traverse(|color| try_parse_color(&color))?,
220 editor_active_line_number: vscode_colors
221 .editor_foreground
222 .as_ref()
223 .traverse(|color| try_parse_color(&color))?,
224 terminal_background: vscode_colors
225 .terminal_background
226 .as_ref()
227 .traverse(|color| try_parse_color(&color))?,
228 terminal_ansi_bright_black: vscode_colors
229 .terminal_ansi_bright_black
230 .as_ref()
231 .traverse(|color| try_parse_color(&color))?,
232 terminal_ansi_bright_red: vscode_colors
233 .terminal_ansi_bright_red
234 .as_ref()
235 .traverse(|color| try_parse_color(&color))?,
236 terminal_ansi_bright_green: vscode_colors
237 .terminal_ansi_bright_green
238 .as_ref()
239 .traverse(|color| try_parse_color(&color))?,
240 terminal_ansi_bright_yellow: vscode_colors
241 .terminal_ansi_bright_yellow
242 .as_ref()
243 .traverse(|color| try_parse_color(&color))?,
244 terminal_ansi_bright_blue: vscode_colors
245 .terminal_ansi_bright_blue
246 .as_ref()
247 .traverse(|color| try_parse_color(&color))?,
248 terminal_ansi_bright_magenta: vscode_colors
249 .terminal_ansi_bright_magenta
250 .as_ref()
251 .traverse(|color| try_parse_color(&color))?,
252 terminal_ansi_bright_cyan: vscode_colors
253 .terminal_ansi_bright_cyan
254 .as_ref()
255 .traverse(|color| try_parse_color(&color))?,
256 terminal_ansi_bright_white: vscode_colors
257 .terminal_ansi_bright_white
258 .as_ref()
259 .traverse(|color| try_parse_color(&color))?,
260 terminal_ansi_black: vscode_colors
261 .terminal_ansi_black
262 .as_ref()
263 .traverse(|color| try_parse_color(&color))?,
264 terminal_ansi_red: vscode_colors
265 .terminal_ansi_red
266 .as_ref()
267 .traverse(|color| try_parse_color(&color))?,
268 terminal_ansi_green: vscode_colors
269 .terminal_ansi_green
270 .as_ref()
271 .traverse(|color| try_parse_color(&color))?,
272 terminal_ansi_yellow: vscode_colors
273 .terminal_ansi_yellow
274 .as_ref()
275 .traverse(|color| try_parse_color(&color))?,
276 terminal_ansi_blue: vscode_colors
277 .terminal_ansi_blue
278 .as_ref()
279 .traverse(|color| try_parse_color(&color))?,
280 terminal_ansi_magenta: vscode_colors
281 .terminal_ansi_magenta
282 .as_ref()
283 .traverse(|color| try_parse_color(&color))?,
284 terminal_ansi_cyan: vscode_colors
285 .terminal_ansi_cyan
286 .as_ref()
287 .traverse(|color| try_parse_color(&color))?,
288 terminal_ansi_white: vscode_colors
289 .terminal_ansi_white
290 .as_ref()
291 .traverse(|color| try_parse_color(&color))?,
292 ..Default::default()
293 })
294 }
295
296 fn convert_syntax_theme(&self) -> Result<UserSyntaxTheme> {
297 let mut highlight_styles = IndexMap::new();
298
299 for syntax_token in ZedSyntaxToken::iter() {
300 let override_match = self
301 .syntax_overrides
302 .get(&syntax_token.to_string())
303 .and_then(|scope| {
304 self.theme.token_colors.iter().find(|token_color| {
305 token_color.scope == Some(VsCodeTokenScope::Many(scope.clone()))
306 })
307 });
308
309 let best_match = override_match
310 .or_else(|| syntax_token.find_best_token_color_match(&self.theme.token_colors))
311 .or_else(|| {
312 syntax_token.fallbacks().iter().find_map(|fallback| {
313 fallback.find_best_token_color_match(&self.theme.token_colors)
314 })
315 });
316
317 let Some(token_color) = best_match else {
318 log::warn!("No matching token color found for '{syntax_token}'");
319 continue;
320 };
321
322 log::info!(
323 "Matched '{syntax_token}' to '{}'",
324 token_color
325 .name
326 .clone()
327 .or_else(|| token_color
328 .scope
329 .as_ref()
330 .map(|scope| format!("{:?}", scope)))
331 .unwrap_or_else(|| "no identifier".to_string())
332 );
333
334 let highlight_style = UserHighlightStyle {
335 color: token_color
336 .settings
337 .foreground
338 .as_ref()
339 .traverse(|color| try_parse_color(&color))?,
340 font_style: token_color
341 .settings
342 .font_style
343 .as_ref()
344 .and_then(|style| try_parse_font_style(&style)),
345 font_weight: token_color
346 .settings
347 .font_style
348 .as_ref()
349 .and_then(|style| try_parse_font_weight(&style)),
350 };
351
352 if highlight_style.is_empty() {
353 continue;
354 }
355
356 highlight_styles.insert(syntax_token.to_string(), highlight_style);
357 }
358
359 Ok(UserSyntaxTheme {
360 highlights: highlight_styles.into_iter().collect(),
361 })
362 }
363}