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 created: vscode_colors
79 .editor_gutter_added_background
80 .as_ref()
81 .traverse(|color| try_parse_color(&color))?,
82 modified: vscode_colors
83 .editor_gutter_modified_background
84 .as_ref()
85 .traverse(|color| try_parse_color(&color))?,
86 deleted: vscode_colors
87 .editor_gutter_deleted_background
88 .as_ref()
89 .traverse(|color| try_parse_color(&color))?,
90 conflict: vscode_colors
91 .git_decoration_conflicting_resource_foreground
92 .as_ref()
93 .traverse(|color| try_parse_color(&color))?,
94 error: vscode_colors
95 .error_foreground
96 .as_ref()
97 .traverse(|color| try_parse_color(&color))?,
98 hidden: vscode_colors
99 .tab_inactive_foreground
100 .as_ref()
101 .traverse(|color| try_parse_color(&color))?,
102 hint: vscode_colors
103 .editor_inlay_hint_foreground
104 .as_ref()
105 .traverse(|color| try_parse_color(&color))?
106 .or(vscode_base_status_colors.hint),
107 ignored: vscode_colors
108 .git_decoration_ignored_resource_foreground
109 .as_ref()
110 .traverse(|color| try_parse_color(&color))?,
111 // info: None,
112 // renamed: None,
113 // success: None,
114 warning: vscode_colors
115 .list_warning_foreground
116 .as_ref()
117 .traverse(|color| try_parse_color(&color))?,
118 ..Default::default()
119 })
120 }
121
122 fn convert_theme_colors(&self) -> Result<ThemeColorsRefinement> {
123 let vscode_colors = &self.theme.colors;
124
125 let vscode_panel_border = vscode_colors
126 .panel_border
127 .as_ref()
128 .traverse(|color| try_parse_color(&color))?;
129
130 let vscode_tab_inactive_background = vscode_colors
131 .tab_inactive_background
132 .as_ref()
133 .traverse(|color| try_parse_color(&color))?;
134
135 let vscode_editor_foreground = vscode_colors
136 .editor_foreground
137 .as_ref()
138 .traverse(|color| try_parse_color(&color))?;
139
140 let vscode_editor_background = vscode_colors
141 .editor_background
142 .as_ref()
143 .traverse(|color| try_parse_color(&color))?;
144
145 let vscode_scrollbar_slider_background = vscode_colors
146 .scrollbar_slider_background
147 .as_ref()
148 .traverse(|color| try_parse_color(&color))?;
149
150 let vscode_token_colors_foreground = self
151 .theme
152 .token_colors
153 .iter()
154 .find(|token_color| token_color.scope.is_none())
155 .and_then(|token_color| token_color.settings.foreground.as_ref())
156 .traverse(|color| try_parse_color(&color))
157 .ok()
158 .flatten();
159
160 Ok(ThemeColorsRefinement {
161 border: vscode_panel_border,
162 border_variant: vscode_panel_border,
163 border_focused: vscode_colors
164 .focus_border
165 .as_ref()
166 .traverse(|color| try_parse_color(&color))?,
167 border_disabled: vscode_panel_border,
168 border_selected: vscode_panel_border,
169 border_transparent: vscode_panel_border,
170 elevated_surface_background: vscode_colors
171 .dropdown_background
172 .as_ref()
173 .traverse(|color| try_parse_color(&color))?,
174 surface_background: vscode_colors
175 .panel_background
176 .as_ref()
177 .traverse(|color| try_parse_color(&color))?,
178 background: vscode_editor_background,
179 title_bar_background: vscode_colors
180 .title_bar_active_background
181 .as_ref()
182 .traverse(|color| try_parse_color(&color))?,
183 status_bar_background: vscode_colors
184 .status_bar_background
185 .as_ref()
186 .traverse(|color| try_parse_color(&color))?,
187 element_background: vscode_colors
188 .button_background
189 .as_ref()
190 .traverse(|color| try_parse_color(&color))?,
191 element_hover: vscode_colors
192 .list_hover_background
193 .as_ref()
194 .traverse(|color| try_parse_color(&color))?,
195 element_selected: vscode_colors
196 .list_active_selection_background
197 .as_ref()
198 .traverse(|color| try_parse_color(&color))?,
199 ghost_element_hover: vscode_colors
200 .list_hover_background
201 .as_ref()
202 .traverse(|color| try_parse_color(&color))?,
203 ghost_element_selected: vscode_colors
204 .list_active_selection_background
205 .as_ref()
206 .traverse(|color| try_parse_color(&color))?,
207 drop_target_background: vscode_colors
208 .list_drop_background
209 .as_ref()
210 .traverse(|color| try_parse_color(&color))?,
211 text: vscode_colors
212 .foreground
213 .as_ref()
214 .traverse(|color| try_parse_color(&color))?
215 .or(vscode_token_colors_foreground),
216 text_muted: vscode_colors
217 .tab_inactive_foreground
218 .as_ref()
219 .traverse(|color| try_parse_color(&color))?,
220 tab_bar_background: vscode_colors
221 .editor_group_header_tabs_background
222 .as_ref()
223 .traverse(|color| try_parse_color(&color))?,
224 tab_active_background: vscode_colors
225 .tab_active_background
226 .as_ref()
227 .traverse(|color| try_parse_color(&color))?
228 .or(vscode_tab_inactive_background),
229 tab_inactive_background: vscode_tab_inactive_background,
230 toolbar_background: vscode_colors
231 .breadcrumb_background
232 .as_ref()
233 .traverse(|color| try_parse_color(&color))?
234 .or(vscode_editor_background),
235 editor_foreground: vscode_editor_foreground.or(vscode_token_colors_foreground),
236 editor_background: vscode_editor_background,
237 editor_gutter_background: vscode_editor_background,
238 editor_line_number: vscode_colors
239 .editor_line_number_foreground
240 .as_ref()
241 .traverse(|color| try_parse_color(&color))?,
242 editor_active_line_number: vscode_colors
243 .editor_foreground
244 .as_ref()
245 .traverse(|color| try_parse_color(&color))?,
246 scrollbar_track_background: vscode_editor_background,
247 scrollbar_track_border: vscode_colors
248 .editor_overview_ruler_border
249 .as_ref()
250 .traverse(|color| try_parse_color(&color))?,
251 scrollbar_thumb_background: vscode_scrollbar_slider_background,
252 scrollbar_thumb_border: vscode_scrollbar_slider_background,
253 scrollbar_thumb_hover_background: vscode_colors
254 .scrollbar_slider_hover_background
255 .as_ref()
256 .traverse(|color| try_parse_color(&color))?,
257 terminal_background: vscode_colors
258 .terminal_background
259 .as_ref()
260 .traverse(|color| try_parse_color(&color))?,
261 terminal_ansi_bright_black: vscode_colors
262 .terminal_ansi_bright_black
263 .as_ref()
264 .traverse(|color| try_parse_color(&color))?,
265 terminal_ansi_bright_red: vscode_colors
266 .terminal_ansi_bright_red
267 .as_ref()
268 .traverse(|color| try_parse_color(&color))?,
269 terminal_ansi_bright_green: vscode_colors
270 .terminal_ansi_bright_green
271 .as_ref()
272 .traverse(|color| try_parse_color(&color))?,
273 terminal_ansi_bright_yellow: vscode_colors
274 .terminal_ansi_bright_yellow
275 .as_ref()
276 .traverse(|color| try_parse_color(&color))?,
277 terminal_ansi_bright_blue: vscode_colors
278 .terminal_ansi_bright_blue
279 .as_ref()
280 .traverse(|color| try_parse_color(&color))?,
281 terminal_ansi_bright_magenta: vscode_colors
282 .terminal_ansi_bright_magenta
283 .as_ref()
284 .traverse(|color| try_parse_color(&color))?,
285 terminal_ansi_bright_cyan: vscode_colors
286 .terminal_ansi_bright_cyan
287 .as_ref()
288 .traverse(|color| try_parse_color(&color))?,
289 terminal_ansi_bright_white: vscode_colors
290 .terminal_ansi_bright_white
291 .as_ref()
292 .traverse(|color| try_parse_color(&color))?,
293 terminal_ansi_black: vscode_colors
294 .terminal_ansi_black
295 .as_ref()
296 .traverse(|color| try_parse_color(&color))?,
297 terminal_ansi_red: vscode_colors
298 .terminal_ansi_red
299 .as_ref()
300 .traverse(|color| try_parse_color(&color))?,
301 terminal_ansi_green: vscode_colors
302 .terminal_ansi_green
303 .as_ref()
304 .traverse(|color| try_parse_color(&color))?,
305 terminal_ansi_yellow: vscode_colors
306 .terminal_ansi_yellow
307 .as_ref()
308 .traverse(|color| try_parse_color(&color))?,
309 terminal_ansi_blue: vscode_colors
310 .terminal_ansi_blue
311 .as_ref()
312 .traverse(|color| try_parse_color(&color))?,
313 terminal_ansi_magenta: vscode_colors
314 .terminal_ansi_magenta
315 .as_ref()
316 .traverse(|color| try_parse_color(&color))?,
317 terminal_ansi_cyan: vscode_colors
318 .terminal_ansi_cyan
319 .as_ref()
320 .traverse(|color| try_parse_color(&color))?,
321 terminal_ansi_white: vscode_colors
322 .terminal_ansi_white
323 .as_ref()
324 .traverse(|color| try_parse_color(&color))?,
325 ..Default::default()
326 })
327 }
328
329 fn convert_syntax_theme(&self) -> Result<UserSyntaxTheme> {
330 let mut highlight_styles = IndexMap::new();
331
332 for syntax_token in ZedSyntaxToken::iter() {
333 let override_match = self
334 .syntax_overrides
335 .get(&syntax_token.to_string())
336 .and_then(|scope| {
337 self.theme.token_colors.iter().find(|token_color| {
338 token_color.scope == Some(VsCodeTokenScope::Many(scope.clone()))
339 })
340 });
341
342 let best_match = override_match
343 .or_else(|| syntax_token.find_best_token_color_match(&self.theme.token_colors))
344 .or_else(|| {
345 syntax_token.fallbacks().iter().find_map(|fallback| {
346 fallback.find_best_token_color_match(&self.theme.token_colors)
347 })
348 });
349
350 let Some(token_color) = best_match else {
351 log::warn!("No matching token color found for '{syntax_token}'");
352 continue;
353 };
354
355 log::info!(
356 "Matched '{syntax_token}' to '{}'",
357 token_color
358 .name
359 .clone()
360 .or_else(|| token_color
361 .scope
362 .as_ref()
363 .map(|scope| format!("{:?}", scope)))
364 .unwrap_or_else(|| "no identifier".to_string())
365 );
366
367 let highlight_style = UserHighlightStyle {
368 color: token_color
369 .settings
370 .foreground
371 .as_ref()
372 .traverse(|color| try_parse_color(&color))?,
373 font_style: token_color
374 .settings
375 .font_style
376 .as_ref()
377 .and_then(|style| try_parse_font_style(&style)),
378 font_weight: token_color
379 .settings
380 .font_style
381 .as_ref()
382 .and_then(|style| try_parse_font_weight(&style)),
383 };
384
385 if highlight_style.is_empty() {
386 continue;
387 }
388
389 highlight_styles.insert(syntax_token.to_string(), highlight_style);
390 }
391
392 Ok(UserSyntaxTheme {
393 highlights: highlight_styles.into_iter().collect(),
394 })
395 }
396}