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            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}