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 link_text_hover: vscode_colors
221 .text_link_active_foreground
222 .as_ref()
223 .traverse(|color| try_parse_color(&color))?,
224 tab_bar_background: vscode_colors
225 .editor_group_header_tabs_background
226 .as_ref()
227 .traverse(|color| try_parse_color(&color))?,
228 tab_active_background: vscode_colors
229 .tab_active_background
230 .as_ref()
231 .traverse(|color| try_parse_color(&color))?
232 .or(vscode_tab_inactive_background),
233 tab_inactive_background: vscode_tab_inactive_background,
234 toolbar_background: vscode_colors
235 .breadcrumb_background
236 .as_ref()
237 .traverse(|color| try_parse_color(&color))?
238 .or(vscode_editor_background),
239 editor_foreground: vscode_editor_foreground.or(vscode_token_colors_foreground),
240 editor_background: vscode_editor_background,
241 editor_gutter_background: vscode_editor_background,
242 editor_line_number: vscode_colors
243 .editor_line_number_foreground
244 .as_ref()
245 .traverse(|color| try_parse_color(&color))?,
246 editor_active_line_number: vscode_colors
247 .editor_foreground
248 .as_ref()
249 .traverse(|color| try_parse_color(&color))?,
250 editor_wrap_guide: vscode_panel_border,
251 editor_active_wrap_guide: vscode_panel_border,
252 scrollbar_track_background: vscode_editor_background,
253 scrollbar_track_border: vscode_colors
254 .editor_overview_ruler_border
255 .as_ref()
256 .traverse(|color| try_parse_color(&color))?,
257 scrollbar_thumb_background: vscode_scrollbar_slider_background,
258 scrollbar_thumb_border: vscode_scrollbar_slider_background,
259 scrollbar_thumb_hover_background: vscode_colors
260 .scrollbar_slider_hover_background
261 .as_ref()
262 .traverse(|color| try_parse_color(&color))?,
263 terminal_background: vscode_colors
264 .terminal_background
265 .as_ref()
266 .traverse(|color| try_parse_color(&color))?,
267 terminal_ansi_bright_black: vscode_colors
268 .terminal_ansi_bright_black
269 .as_ref()
270 .traverse(|color| try_parse_color(&color))?,
271 terminal_ansi_bright_red: vscode_colors
272 .terminal_ansi_bright_red
273 .as_ref()
274 .traverse(|color| try_parse_color(&color))?,
275 terminal_ansi_bright_green: vscode_colors
276 .terminal_ansi_bright_green
277 .as_ref()
278 .traverse(|color| try_parse_color(&color))?,
279 terminal_ansi_bright_yellow: vscode_colors
280 .terminal_ansi_bright_yellow
281 .as_ref()
282 .traverse(|color| try_parse_color(&color))?,
283 terminal_ansi_bright_blue: vscode_colors
284 .terminal_ansi_bright_blue
285 .as_ref()
286 .traverse(|color| try_parse_color(&color))?,
287 terminal_ansi_bright_magenta: vscode_colors
288 .terminal_ansi_bright_magenta
289 .as_ref()
290 .traverse(|color| try_parse_color(&color))?,
291 terminal_ansi_bright_cyan: vscode_colors
292 .terminal_ansi_bright_cyan
293 .as_ref()
294 .traverse(|color| try_parse_color(&color))?,
295 terminal_ansi_bright_white: vscode_colors
296 .terminal_ansi_bright_white
297 .as_ref()
298 .traverse(|color| try_parse_color(&color))?,
299 terminal_ansi_black: vscode_colors
300 .terminal_ansi_black
301 .as_ref()
302 .traverse(|color| try_parse_color(&color))?,
303 terminal_ansi_red: vscode_colors
304 .terminal_ansi_red
305 .as_ref()
306 .traverse(|color| try_parse_color(&color))?,
307 terminal_ansi_green: vscode_colors
308 .terminal_ansi_green
309 .as_ref()
310 .traverse(|color| try_parse_color(&color))?,
311 terminal_ansi_yellow: vscode_colors
312 .terminal_ansi_yellow
313 .as_ref()
314 .traverse(|color| try_parse_color(&color))?,
315 terminal_ansi_blue: vscode_colors
316 .terminal_ansi_blue
317 .as_ref()
318 .traverse(|color| try_parse_color(&color))?,
319 terminal_ansi_magenta: vscode_colors
320 .terminal_ansi_magenta
321 .as_ref()
322 .traverse(|color| try_parse_color(&color))?,
323 terminal_ansi_cyan: vscode_colors
324 .terminal_ansi_cyan
325 .as_ref()
326 .traverse(|color| try_parse_color(&color))?,
327 terminal_ansi_white: vscode_colors
328 .terminal_ansi_white
329 .as_ref()
330 .traverse(|color| try_parse_color(&color))?,
331 ..Default::default()
332 })
333 }
334
335 fn convert_syntax_theme(&self) -> Result<UserSyntaxTheme> {
336 let mut highlight_styles = IndexMap::new();
337
338 for syntax_token in ZedSyntaxToken::iter() {
339 let override_match = self
340 .syntax_overrides
341 .get(&syntax_token.to_string())
342 .and_then(|scope| {
343 self.theme.token_colors.iter().find(|token_color| {
344 token_color.scope == Some(VsCodeTokenScope::Many(scope.clone()))
345 })
346 });
347
348 let best_match = override_match
349 .or_else(|| syntax_token.find_best_token_color_match(&self.theme.token_colors))
350 .or_else(|| {
351 syntax_token.fallbacks().iter().find_map(|fallback| {
352 fallback.find_best_token_color_match(&self.theme.token_colors)
353 })
354 });
355
356 let Some(token_color) = best_match else {
357 log::warn!("No matching token color found for '{syntax_token}'");
358 continue;
359 };
360
361 log::info!(
362 "Matched '{syntax_token}' to '{}'",
363 token_color
364 .name
365 .clone()
366 .or_else(|| token_color
367 .scope
368 .as_ref()
369 .map(|scope| format!("{:?}", scope)))
370 .unwrap_or_else(|| "no identifier".to_string())
371 );
372
373 let highlight_style = UserHighlightStyle {
374 color: token_color
375 .settings
376 .foreground
377 .as_ref()
378 .traverse(|color| try_parse_color(&color))?,
379 font_style: token_color
380 .settings
381 .font_style
382 .as_ref()
383 .and_then(|style| try_parse_font_style(&style)),
384 font_weight: token_color
385 .settings
386 .font_style
387 .as_ref()
388 .and_then(|style| try_parse_font_weight(&style)),
389 };
390
391 if highlight_style.is_empty() {
392 continue;
393 }
394
395 highlight_styles.insert(syntax_token.to_string(), highlight_style);
396 }
397
398 Ok(UserSyntaxTheme {
399 highlights: highlight_styles.into_iter().collect(),
400 })
401 }
402}