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: vscode_colors
79 .git_decoration_conflicting_resource_foreground
80 .as_ref()
81 .traverse(|color| try_parse_color(&color))?,
82 created: vscode_colors
83 .git_decoration_untracked_resource_foreground
84 .as_ref()
85 .traverse(|color| try_parse_color(&color))?,
86 deleted: vscode_colors
87 .error_foreground
88 .as_ref()
89 .traverse(|color| try_parse_color(&color))?,
90 error: vscode_colors
91 .error_foreground
92 .as_ref()
93 .traverse(|color| try_parse_color(&color))?,
94 hidden: vscode_colors
95 .tab_inactive_foreground
96 .as_ref()
97 .traverse(|color| try_parse_color(&color))?,
98 hint: vscode_colors
99 .editor_inlay_hint_foreground
100 .as_ref()
101 .traverse(|color| try_parse_color(&color))?
102 .or(vscode_base_status_colors.hint),
103 ignored: vscode_colors
104 .git_decoration_ignored_resource_foreground
105 .as_ref()
106 .traverse(|color| try_parse_color(&color))?,
107 // info: None,
108 modified: vscode_colors
109 .git_decoration_modified_resource_foreground
110 .as_ref()
111 .traverse(|color| try_parse_color(&color))?,
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_editor_background = vscode_colors
126 .editor_background
127 .as_ref()
128 .traverse(|color| try_parse_color(&color))?;
129
130 Ok(ThemeColorsRefinement {
131 border: vscode_colors
132 .panel_border
133 .as_ref()
134 .traverse(|color| try_parse_color(&color))?,
135 border_variant: vscode_colors
136 .panel_border
137 .as_ref()
138 .traverse(|color| try_parse_color(&color))?,
139 border_focused: vscode_colors
140 .focus_border
141 .as_ref()
142 .traverse(|color| try_parse_color(&color))?,
143 border_disabled: vscode_colors
144 .panel_border
145 .as_ref()
146 .traverse(|color| try_parse_color(&color))?,
147 border_selected: vscode_colors
148 .panel_border
149 .as_ref()
150 .traverse(|color| try_parse_color(&color))?,
151 border_transparent: vscode_colors
152 .panel_border
153 .as_ref()
154 .traverse(|color| try_parse_color(&color))?,
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 tab_bar_background: vscode_colors
211 .editor_group_header_tabs_background
212 .as_ref()
213 .traverse(|color| try_parse_color(&color))?,
214 tab_active_background: vscode_colors
215 .tab_active_background
216 .as_ref()
217 .traverse(|color| try_parse_color(&color))?,
218 tab_inactive_background: vscode_colors
219 .tab_inactive_background
220 .as_ref()
221 .traverse(|color| try_parse_color(&color))?,
222 toolbar_background: vscode_colors
223 .breadcrumb_background
224 .as_ref()
225 .traverse(|color| try_parse_color(&color))?
226 .or(vscode_editor_background),
227 editor_background: vscode_editor_background,
228 editor_gutter_background: vscode_editor_background,
229 editor_line_number: vscode_colors
230 .editor_line_number_foreground
231 .as_ref()
232 .traverse(|color| try_parse_color(&color))?,
233 editor_active_line_number: vscode_colors
234 .editor_foreground
235 .as_ref()
236 .traverse(|color| try_parse_color(&color))?,
237 terminal_background: vscode_colors
238 .terminal_background
239 .as_ref()
240 .traverse(|color| try_parse_color(&color))?,
241 terminal_ansi_bright_black: vscode_colors
242 .terminal_ansi_bright_black
243 .as_ref()
244 .traverse(|color| try_parse_color(&color))?,
245 terminal_ansi_bright_red: vscode_colors
246 .terminal_ansi_bright_red
247 .as_ref()
248 .traverse(|color| try_parse_color(&color))?,
249 terminal_ansi_bright_green: vscode_colors
250 .terminal_ansi_bright_green
251 .as_ref()
252 .traverse(|color| try_parse_color(&color))?,
253 terminal_ansi_bright_yellow: vscode_colors
254 .terminal_ansi_bright_yellow
255 .as_ref()
256 .traverse(|color| try_parse_color(&color))?,
257 terminal_ansi_bright_blue: vscode_colors
258 .terminal_ansi_bright_blue
259 .as_ref()
260 .traverse(|color| try_parse_color(&color))?,
261 terminal_ansi_bright_magenta: vscode_colors
262 .terminal_ansi_bright_magenta
263 .as_ref()
264 .traverse(|color| try_parse_color(&color))?,
265 terminal_ansi_bright_cyan: vscode_colors
266 .terminal_ansi_bright_cyan
267 .as_ref()
268 .traverse(|color| try_parse_color(&color))?,
269 terminal_ansi_bright_white: vscode_colors
270 .terminal_ansi_bright_white
271 .as_ref()
272 .traverse(|color| try_parse_color(&color))?,
273 terminal_ansi_black: vscode_colors
274 .terminal_ansi_black
275 .as_ref()
276 .traverse(|color| try_parse_color(&color))?,
277 terminal_ansi_red: vscode_colors
278 .terminal_ansi_red
279 .as_ref()
280 .traverse(|color| try_parse_color(&color))?,
281 terminal_ansi_green: vscode_colors
282 .terminal_ansi_green
283 .as_ref()
284 .traverse(|color| try_parse_color(&color))?,
285 terminal_ansi_yellow: vscode_colors
286 .terminal_ansi_yellow
287 .as_ref()
288 .traverse(|color| try_parse_color(&color))?,
289 terminal_ansi_blue: vscode_colors
290 .terminal_ansi_blue
291 .as_ref()
292 .traverse(|color| try_parse_color(&color))?,
293 terminal_ansi_magenta: vscode_colors
294 .terminal_ansi_magenta
295 .as_ref()
296 .traverse(|color| try_parse_color(&color))?,
297 terminal_ansi_cyan: vscode_colors
298 .terminal_ansi_cyan
299 .as_ref()
300 .traverse(|color| try_parse_color(&color))?,
301 terminal_ansi_white: vscode_colors
302 .terminal_ansi_white
303 .as_ref()
304 .traverse(|color| try_parse_color(&color))?,
305 ..Default::default()
306 })
307 }
308
309 fn convert_syntax_theme(&self) -> Result<UserSyntaxTheme> {
310 let mut highlight_styles = IndexMap::new();
311
312 for syntax_token in ZedSyntaxToken::iter() {
313 let override_match = self
314 .syntax_overrides
315 .get(&syntax_token.to_string())
316 .and_then(|scope| {
317 self.theme.token_colors.iter().find(|token_color| {
318 token_color.scope == Some(VsCodeTokenScope::Many(scope.clone()))
319 })
320 });
321
322 let best_match = override_match
323 .or_else(|| syntax_token.find_best_token_color_match(&self.theme.token_colors))
324 .or_else(|| {
325 syntax_token.fallbacks().iter().find_map(|fallback| {
326 fallback.find_best_token_color_match(&self.theme.token_colors)
327 })
328 });
329
330 let Some(token_color) = best_match else {
331 log::warn!("No matching token color found for '{syntax_token}'");
332 continue;
333 };
334
335 log::info!(
336 "Matched '{syntax_token}' to '{}'",
337 token_color
338 .name
339 .clone()
340 .or_else(|| token_color
341 .scope
342 .as_ref()
343 .map(|scope| format!("{:?}", scope)))
344 .unwrap_or_else(|| "no identifier".to_string())
345 );
346
347 let highlight_style = UserHighlightStyle {
348 color: token_color
349 .settings
350 .foreground
351 .as_ref()
352 .traverse(|color| try_parse_color(&color))?,
353 font_style: token_color
354 .settings
355 .font_style
356 .as_ref()
357 .and_then(|style| try_parse_font_style(&style)),
358 font_weight: token_color
359 .settings
360 .font_style
361 .as_ref()
362 .and_then(|style| try_parse_font_weight(&style)),
363 };
364
365 if highlight_style.is_empty() {
366 continue;
367 }
368
369 highlight_styles.insert(syntax_token.to_string(), highlight_style);
370 }
371
372 Ok(UserSyntaxTheme {
373 highlights: highlight_styles.into_iter().collect(),
374 })
375 }
376}