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