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