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,
 60            appearance,
 61            styles: UserThemeStylesRefinement {
 62                colors: theme_colors_refinements,
 63                status: status_color_refinements,
 64                player: None,
 65                syntax: Some(syntax_theme),
 66            },
 67        })
 68    }
 69
 70    fn convert_status_colors(&self) -> Result<StatusColorsRefinement> {
 71        let vscode_colors = &self.theme.colors;
 72
 73        let vscode_base_status_colors = StatusColorsRefinement {
 74            hint: Some(rgba(0x969696ff).into()),
 75            ..Default::default()
 76        };
 77
 78        Ok(StatusColorsRefinement {
 79            created: vscode_colors
 80                .editor_gutter_added_background
 81                .as_ref()
 82                .traverse(|color| try_parse_color(&color))?,
 83            modified: vscode_colors
 84                .editor_gutter_modified_background
 85                .as_ref()
 86                .traverse(|color| try_parse_color(&color))?,
 87            deleted: vscode_colors
 88                .editor_gutter_deleted_background
 89                .as_ref()
 90                .traverse(|color| try_parse_color(&color))?,
 91            conflict: vscode_colors
 92                .git_decoration_conflicting_resource_foreground
 93                .as_ref()
 94                .traverse(|color| try_parse_color(&color))?,
 95            error: vscode_colors
 96                .error_foreground
 97                .as_ref()
 98                .traverse(|color| try_parse_color(&color))?,
 99            hidden: vscode_colors
100                .tab_inactive_foreground
101                .as_ref()
102                .traverse(|color| try_parse_color(&color))?,
103            hint: vscode_colors
104                .editor_inlay_hint_foreground
105                .as_ref()
106                .traverse(|color| try_parse_color(&color))?
107                .or(vscode_base_status_colors.hint),
108            ignored: vscode_colors
109                .git_decoration_ignored_resource_foreground
110                .as_ref()
111                .traverse(|color| try_parse_color(&color))?,
112            // info: None,
113            // renamed: None,
114            // success: None,
115            warning: vscode_colors
116                .list_warning_foreground
117                .as_ref()
118                .traverse(|color| try_parse_color(&color))?,
119            ..Default::default()
120        })
121    }
122
123    fn convert_theme_colors(&self) -> Result<ThemeColorsRefinement> {
124        let vscode_colors = &self.theme.colors;
125
126        let vscode_panel_border = vscode_colors
127            .panel_border
128            .as_ref()
129            .traverse(|color| try_parse_color(&color))?;
130
131        let vscode_tab_inactive_background = vscode_colors
132            .tab_inactive_background
133            .as_ref()
134            .traverse(|color| try_parse_color(&color))?;
135
136        let vscode_editor_foreground = vscode_colors
137            .editor_foreground
138            .as_ref()
139            .traverse(|color| try_parse_color(&color))?;
140
141        let vscode_editor_background = vscode_colors
142            .editor_background
143            .as_ref()
144            .traverse(|color| try_parse_color(&color))?;
145
146        let vscode_scrollbar_slider_background = vscode_colors
147            .scrollbar_slider_background
148            .as_ref()
149            .traverse(|color| try_parse_color(&color))?;
150
151        let vscode_token_colors_foreground = self
152            .theme
153            .token_colors
154            .iter()
155            .find(|token_color| token_color.scope.is_none())
156            .and_then(|token_color| token_color.settings.foreground.as_ref())
157            .traverse(|color| try_parse_color(&color))
158            .ok()
159            .flatten();
160
161        Ok(ThemeColorsRefinement {
162            border: vscode_panel_border,
163            border_variant: vscode_panel_border,
164            border_focused: vscode_colors
165                .focus_border
166                .as_ref()
167                .traverse(|color| try_parse_color(&color))?,
168            border_disabled: vscode_panel_border,
169            border_selected: vscode_panel_border,
170            border_transparent: vscode_panel_border,
171            elevated_surface_background: vscode_colors
172                .dropdown_background
173                .as_ref()
174                .traverse(|color| try_parse_color(&color))?,
175            surface_background: vscode_colors
176                .panel_background
177                .as_ref()
178                .traverse(|color| try_parse_color(&color))?,
179            background: vscode_editor_background,
180            title_bar_background: vscode_colors
181                .title_bar_active_background
182                .as_ref()
183                .traverse(|color| try_parse_color(&color))?,
184            status_bar_background: vscode_colors
185                .status_bar_background
186                .as_ref()
187                .traverse(|color| try_parse_color(&color))?,
188            element_background: vscode_colors
189                .button_background
190                .as_ref()
191                .traverse(|color| try_parse_color(&color))?,
192            element_hover: vscode_colors
193                .list_hover_background
194                .as_ref()
195                .traverse(|color| try_parse_color(&color))?,
196            element_selected: vscode_colors
197                .list_active_selection_background
198                .as_ref()
199                .traverse(|color| try_parse_color(&color))?,
200            ghost_element_hover: vscode_colors
201                .list_hover_background
202                .as_ref()
203                .traverse(|color| try_parse_color(&color))?,
204            ghost_element_selected: vscode_colors
205                .list_active_selection_background
206                .as_ref()
207                .traverse(|color| try_parse_color(&color))?,
208            drop_target_background: vscode_colors
209                .list_drop_background
210                .as_ref()
211                .traverse(|color| try_parse_color(&color))?,
212            text: vscode_colors
213                .foreground
214                .as_ref()
215                .traverse(|color| try_parse_color(&color))?
216                .or(vscode_token_colors_foreground),
217            text_muted: vscode_colors
218                .tab_inactive_foreground
219                .as_ref()
220                .traverse(|color| try_parse_color(&color))?,
221            link_text_hover: vscode_colors
222                .text_link_active_foreground
223                .as_ref()
224                .traverse(|color| try_parse_color(&color))?,
225            tab_bar_background: vscode_colors
226                .editor_group_header_tabs_background
227                .as_ref()
228                .traverse(|color| try_parse_color(&color))?,
229            tab_active_background: vscode_colors
230                .tab_active_background
231                .as_ref()
232                .traverse(|color| try_parse_color(&color))?
233                .or(vscode_tab_inactive_background),
234            tab_inactive_background: vscode_tab_inactive_background,
235            toolbar_background: vscode_colors
236                .breadcrumb_background
237                .as_ref()
238                .traverse(|color| try_parse_color(&color))?
239                .or(vscode_editor_background),
240            editor_foreground: vscode_editor_foreground.or(vscode_token_colors_foreground),
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            editor_wrap_guide: vscode_panel_border,
252            editor_active_wrap_guide: vscode_panel_border,
253            scrollbar_track_background: vscode_editor_background,
254            scrollbar_track_border: vscode_colors
255                .editor_overview_ruler_border
256                .as_ref()
257                .traverse(|color| try_parse_color(&color))?,
258            scrollbar_thumb_background: vscode_scrollbar_slider_background,
259            scrollbar_thumb_border: vscode_scrollbar_slider_background,
260            scrollbar_thumb_hover_background: vscode_colors
261                .scrollbar_slider_hover_background
262                .as_ref()
263                .traverse(|color| try_parse_color(&color))?,
264            terminal_background: vscode_colors
265                .terminal_background
266                .as_ref()
267                .traverse(|color| try_parse_color(&color))?,
268            terminal_ansi_bright_black: vscode_colors
269                .terminal_ansi_bright_black
270                .as_ref()
271                .traverse(|color| try_parse_color(&color))?,
272            terminal_ansi_bright_red: vscode_colors
273                .terminal_ansi_bright_red
274                .as_ref()
275                .traverse(|color| try_parse_color(&color))?,
276            terminal_ansi_bright_green: vscode_colors
277                .terminal_ansi_bright_green
278                .as_ref()
279                .traverse(|color| try_parse_color(&color))?,
280            terminal_ansi_bright_yellow: vscode_colors
281                .terminal_ansi_bright_yellow
282                .as_ref()
283                .traverse(|color| try_parse_color(&color))?,
284            terminal_ansi_bright_blue: vscode_colors
285                .terminal_ansi_bright_blue
286                .as_ref()
287                .traverse(|color| try_parse_color(&color))?,
288            terminal_ansi_bright_magenta: vscode_colors
289                .terminal_ansi_bright_magenta
290                .as_ref()
291                .traverse(|color| try_parse_color(&color))?,
292            terminal_ansi_bright_cyan: vscode_colors
293                .terminal_ansi_bright_cyan
294                .as_ref()
295                .traverse(|color| try_parse_color(&color))?,
296            terminal_ansi_bright_white: vscode_colors
297                .terminal_ansi_bright_white
298                .as_ref()
299                .traverse(|color| try_parse_color(&color))?,
300            terminal_ansi_black: vscode_colors
301                .terminal_ansi_black
302                .as_ref()
303                .traverse(|color| try_parse_color(&color))?,
304            terminal_ansi_red: vscode_colors
305                .terminal_ansi_red
306                .as_ref()
307                .traverse(|color| try_parse_color(&color))?,
308            terminal_ansi_green: vscode_colors
309                .terminal_ansi_green
310                .as_ref()
311                .traverse(|color| try_parse_color(&color))?,
312            terminal_ansi_yellow: vscode_colors
313                .terminal_ansi_yellow
314                .as_ref()
315                .traverse(|color| try_parse_color(&color))?,
316            terminal_ansi_blue: vscode_colors
317                .terminal_ansi_blue
318                .as_ref()
319                .traverse(|color| try_parse_color(&color))?,
320            terminal_ansi_magenta: vscode_colors
321                .terminal_ansi_magenta
322                .as_ref()
323                .traverse(|color| try_parse_color(&color))?,
324            terminal_ansi_cyan: vscode_colors
325                .terminal_ansi_cyan
326                .as_ref()
327                .traverse(|color| try_parse_color(&color))?,
328            terminal_ansi_white: vscode_colors
329                .terminal_ansi_white
330                .as_ref()
331                .traverse(|color| try_parse_color(&color))?,
332            ..Default::default()
333        })
334    }
335
336    fn convert_syntax_theme(&self) -> Result<UserSyntaxTheme> {
337        let mut highlight_styles = IndexMap::new();
338
339        for syntax_token in ZedSyntaxToken::iter() {
340            let override_match = self
341                .syntax_overrides
342                .get(&syntax_token.to_string())
343                .and_then(|scope| {
344                    self.theme.token_colors.iter().find(|token_color| {
345                        token_color.scope == Some(VsCodeTokenScope::Many(scope.clone()))
346                    })
347                });
348
349            let best_match = override_match
350                .or_else(|| syntax_token.find_best_token_color_match(&self.theme.token_colors))
351                .or_else(|| {
352                    syntax_token.fallbacks().iter().find_map(|fallback| {
353                        fallback.find_best_token_color_match(&self.theme.token_colors)
354                    })
355                });
356
357            let Some(token_color) = best_match else {
358                log::warn!("No matching token color found for '{syntax_token}'");
359                continue;
360            };
361
362            log::info!(
363                "Matched '{syntax_token}' to '{}'",
364                token_color
365                    .name
366                    .clone()
367                    .or_else(|| token_color
368                        .scope
369                        .as_ref()
370                        .map(|scope| format!("{:?}", scope)))
371                    .unwrap_or_else(|| "no identifier".to_string())
372            );
373
374            let highlight_style = UserHighlightStyle {
375                color: token_color
376                    .settings
377                    .foreground
378                    .as_ref()
379                    .traverse(|color| try_parse_color(&color))?,
380                font_style: token_color
381                    .settings
382                    .font_style
383                    .as_ref()
384                    .and_then(|style| try_parse_font_style(&style)),
385                font_weight: token_color
386                    .settings
387                    .font_style
388                    .as_ref()
389                    .and_then(|style| try_parse_font_weight(&style)),
390            };
391
392            if highlight_style.is_empty() {
393                continue;
394            }
395
396            highlight_styles.insert(syntax_token.to_string(), highlight_style);
397        }
398
399        Ok(UserSyntaxTheme {
400            highlights: highlight_styles.into_iter().collect(),
401        })
402    }
403}