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