converter.rs

  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}