converter.rs

  1use anyhow::Result;
  2use indexmap::IndexMap;
  3use strum::IntoEnumIterator;
  4use theme::{
  5    FontStyleContent, FontWeightContent, HighlightStyleContent, StatusColorsContent,
  6    ThemeColorsContent, ThemeContent, ThemeStyleContent,
  7};
  8
  9use crate::ThemeMetadata;
 10use crate::vscode::{VsCodeTheme, VsCodeTokenScope};
 11
 12use super::ZedSyntaxToken;
 13
 14pub(crate) fn try_parse_font_weight(font_style: &str) -> Option<FontWeightContent> {
 15    match font_style {
 16        style if style.contains("bold") => Some(FontWeightContent::Bold),
 17        _ => None,
 18    }
 19}
 20
 21pub(crate) fn try_parse_font_style(font_style: &str) -> Option<FontStyleContent> {
 22    match font_style {
 23        style if style.contains("italic") => Some(FontStyleContent::Italic),
 24        style if style.contains("oblique") => Some(FontStyleContent::Oblique),
 25        _ => None,
 26    }
 27}
 28
 29pub struct VsCodeThemeConverter {
 30    theme: VsCodeTheme,
 31    theme_metadata: ThemeMetadata,
 32    syntax_overrides: IndexMap<String, Vec<String>>,
 33}
 34
 35impl VsCodeThemeConverter {
 36    pub fn new(
 37        theme: VsCodeTheme,
 38        theme_metadata: ThemeMetadata,
 39        syntax_overrides: IndexMap<String, Vec<String>>,
 40    ) -> Self {
 41        Self {
 42            theme,
 43            theme_metadata,
 44            syntax_overrides,
 45        }
 46    }
 47
 48    pub fn convert(self) -> Result<ThemeContent> {
 49        let appearance = self.theme_metadata.appearance.into();
 50
 51        let status_colors = self.convert_status_colors()?;
 52        let theme_colors = self.convert_theme_colors()?;
 53        let syntax_theme = self.convert_syntax_theme()?;
 54
 55        Ok(ThemeContent {
 56            name: self.theme_metadata.name,
 57            appearance,
 58            style: ThemeStyleContent {
 59                window_background_appearance: Some(theme::WindowBackgroundContent::Opaque),
 60                accents: Vec::new(), //TODO can we read this from the theme?
 61                colors: theme_colors,
 62                status: status_colors,
 63                players: Vec::new(),
 64                syntax: syntax_theme,
 65            },
 66        })
 67    }
 68
 69    fn convert_status_colors(&self) -> Result<StatusColorsContent> {
 70        let vscode_colors = &self.theme.colors;
 71
 72        let vscode_base_status_colors = StatusColorsContent {
 73            hint: Some("#969696ff".to_string()),
 74            ..Default::default()
 75        };
 76
 77        Ok(StatusColorsContent {
 78            conflict: vscode_colors
 79                .git_decoration
 80                .conflicting_resource_foreground
 81                .clone(),
 82            created: vscode_colors.editor_gutter.added_background.clone(),
 83            deleted: vscode_colors.editor_gutter.deleted_background.clone(),
 84            error: vscode_colors.editor_error.foreground.clone(),
 85            error_background: vscode_colors.editor_error.background.clone(),
 86            error_border: vscode_colors.editor_error.border.clone(),
 87            hidden: vscode_colors.tab.inactive_foreground.clone(),
 88            hint: vscode_colors
 89                .editor_inlay_hint
 90                .foreground
 91                .clone()
 92                .or(vscode_base_status_colors.hint),
 93            hint_border: vscode_colors.editor_hint.border.clone(),
 94            ignored: vscode_colors
 95                .git_decoration
 96                .ignored_resource_foreground
 97                .clone(),
 98            info: vscode_colors.editor_info.foreground.clone(),
 99            info_background: vscode_colors.editor_info.background.clone(),
100            info_border: vscode_colors.editor_info.border.clone(),
101            modified: vscode_colors.editor_gutter.modified_background.clone(),
102            // renamed: None,
103            // success: None,
104            warning: vscode_colors.editor_warning.foreground.clone(),
105            warning_background: vscode_colors.editor_warning.background.clone(),
106            warning_border: vscode_colors.editor_warning.border.clone(),
107            ..Default::default()
108        })
109    }
110
111    fn convert_theme_colors(&self) -> Result<ThemeColorsContent> {
112        let vscode_colors = &self.theme.colors;
113
114        let vscode_panel_border = vscode_colors.panel.border.clone();
115        let vscode_tab_inactive_background = vscode_colors.tab.inactive_background.clone();
116        let vscode_editor_foreground = vscode_colors.editor.foreground.clone();
117        let vscode_editor_background = vscode_colors.editor.background.clone();
118        let vscode_scrollbar_slider_background = vscode_colors.scrollbar_slider.background.clone();
119        let vscode_token_colors_foreground = self
120            .theme
121            .token_colors
122            .iter()
123            .find(|token_color| token_color.scope.is_none())
124            .and_then(|token_color| token_color.settings.foreground.as_ref())
125            .cloned();
126
127        Ok(ThemeColorsContent {
128            border: vscode_panel_border.clone(),
129            border_variant: vscode_panel_border.clone(),
130            border_focused: vscode_colors.focus_border.clone(),
131            border_selected: vscode_panel_border.clone(),
132            border_transparent: vscode_panel_border.clone(),
133            border_disabled: vscode_panel_border.clone(),
134            elevated_surface_background: vscode_colors.dropdown.background.clone(),
135            surface_background: vscode_colors.panel.background.clone(),
136            background: vscode_editor_background.clone(),
137            element_background: vscode_colors.button.background.clone(),
138            element_hover: vscode_colors.list.hover_background.clone(),
139            element_selected: vscode_colors.list.active_selection_background.clone(),
140            drop_target_background: vscode_colors.list.drop_background.clone(),
141            ghost_element_hover: vscode_colors.list.hover_background.clone(),
142            ghost_element_selected: vscode_colors.list.active_selection_background.clone(),
143            text: vscode_colors
144                .foreground
145                .clone()
146                .or(vscode_token_colors_foreground.clone()),
147            text_muted: vscode_colors.tab.inactive_foreground.clone(),
148            status_bar_background: vscode_colors.status_bar.background.clone(),
149            title_bar_background: vscode_colors.title_bar.active_background.clone(),
150            toolbar_background: vscode_colors
151                .breadcrumb
152                .background
153                .clone()
154                .or(vscode_editor_background.clone()),
155            tab_bar_background: vscode_colors.editor_group_header.tabs_background.clone(),
156            tab_inactive_background: vscode_tab_inactive_background.clone(),
157            tab_active_background: vscode_colors
158                .tab
159                .active_background
160                .clone()
161                .or(vscode_tab_inactive_background),
162            search_match_background: vscode_colors.editor.find_match_background.clone(),
163            panel_background: vscode_colors.panel.background.clone(),
164            pane_group_border: vscode_colors.editor_group.border.clone(),
165            scrollbar_thumb_background: vscode_scrollbar_slider_background.clone(),
166            scrollbar_thumb_hover_background: vscode_colors
167                .scrollbar_slider
168                .hover_background
169                .clone(),
170            scrollbar_thumb_active_background: vscode_colors
171                .scrollbar_slider
172                .active_background
173                .clone(),
174            scrollbar_thumb_border: vscode_scrollbar_slider_background,
175            scrollbar_track_background: vscode_editor_background.clone(),
176            scrollbar_track_border: vscode_colors.editor_overview_ruler.border.clone(),
177            minimap_thumb_background: vscode_colors.minimap_slider.background.clone(),
178            minimap_thumb_hover_background: vscode_colors.minimap_slider.hover_background.clone(),
179            minimap_thumb_active_background: vscode_colors.minimap_slider.active_background.clone(),
180            editor_foreground: vscode_editor_foreground.or(vscode_token_colors_foreground),
181            editor_background: vscode_editor_background.clone(),
182            editor_gutter_background: vscode_editor_background,
183            editor_active_line_background: vscode_colors.editor.line_highlight_background.clone(),
184            editor_line_number: vscode_colors.editor_line_number.foreground.clone(),
185            editor_active_line_number: vscode_colors.editor.foreground.clone(),
186            editor_wrap_guide: vscode_panel_border.clone(),
187            editor_active_wrap_guide: vscode_panel_border,
188            editor_document_highlight_bracket_background: vscode_colors
189                .editor_bracket_match
190                .background
191                .clone(),
192            terminal_background: vscode_colors.terminal.background.clone(),
193            terminal_ansi_black: vscode_colors.terminal.ansi_black.clone(),
194            terminal_ansi_bright_black: vscode_colors.terminal.ansi_bright_black.clone(),
195            terminal_ansi_red: vscode_colors.terminal.ansi_red.clone(),
196            terminal_ansi_bright_red: vscode_colors.terminal.ansi_bright_red.clone(),
197            terminal_ansi_green: vscode_colors.terminal.ansi_green.clone(),
198            terminal_ansi_bright_green: vscode_colors.terminal.ansi_bright_green.clone(),
199            terminal_ansi_yellow: vscode_colors.terminal.ansi_yellow.clone(),
200            terminal_ansi_bright_yellow: vscode_colors.terminal.ansi_bright_yellow.clone(),
201            terminal_ansi_blue: vscode_colors.terminal.ansi_blue.clone(),
202            terminal_ansi_bright_blue: vscode_colors.terminal.ansi_bright_blue.clone(),
203            terminal_ansi_magenta: vscode_colors.terminal.ansi_magenta.clone(),
204            terminal_ansi_bright_magenta: vscode_colors.terminal.ansi_bright_magenta.clone(),
205            terminal_ansi_cyan: vscode_colors.terminal.ansi_cyan.clone(),
206            terminal_ansi_bright_cyan: vscode_colors.terminal.ansi_bright_cyan.clone(),
207            terminal_ansi_white: vscode_colors.terminal.ansi_white.clone(),
208            terminal_ansi_bright_white: vscode_colors.terminal.ansi_bright_white.clone(),
209            link_text_hover: vscode_colors.text_link.active_foreground.clone(),
210            ..Default::default()
211        })
212    }
213
214    fn convert_syntax_theme(&self) -> Result<IndexMap<String, HighlightStyleContent>> {
215        let mut highlight_styles = IndexMap::new();
216
217        for syntax_token in ZedSyntaxToken::iter() {
218            let override_match = self
219                .syntax_overrides
220                .get(&syntax_token.to_string())
221                .and_then(|scope| {
222                    self.theme.token_colors.iter().find(|token_color| {
223                        token_color.scope == Some(VsCodeTokenScope::Many(scope.clone()))
224                    })
225                });
226
227            let best_match = override_match
228                .or_else(|| syntax_token.find_best_token_color_match(&self.theme.token_colors))
229                .or_else(|| {
230                    syntax_token.fallbacks().iter().find_map(|fallback| {
231                        fallback.find_best_token_color_match(&self.theme.token_colors)
232                    })
233                });
234
235            let Some(token_color) = best_match else {
236                log::warn!("No matching token color found for '{syntax_token}'");
237                continue;
238            };
239
240            log::info!(
241                "Matched '{syntax_token}' to '{}'",
242                token_color
243                    .name
244                    .clone()
245                    .or_else(|| token_color
246                        .scope
247                        .as_ref()
248                        .map(|scope| format!("{:?}", scope)))
249                    .unwrap_or_else(|| "no identifier".to_string())
250            );
251
252            let highlight_style = HighlightStyleContent {
253                color: token_color.settings.foreground.clone(),
254                background_color: token_color.settings.background.clone(),
255                font_style: token_color
256                    .settings
257                    .font_style
258                    .as_ref()
259                    .and_then(|style| try_parse_font_style(style)),
260                font_weight: token_color
261                    .settings
262                    .font_style
263                    .as_ref()
264                    .and_then(|style| try_parse_font_weight(style)),
265            };
266
267            if highlight_style.is_empty() {
268                continue;
269            }
270
271            highlight_styles.insert(syntax_token.to_string(), highlight_style);
272        }
273
274        Ok(highlight_styles)
275    }
276}