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