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::vscode::{VsCodeTheme, VsCodeTokenScope};
 10use crate::ThemeMetadata;
 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                colors: theme_colors,
 61                status: status_colors,
 62                players: Vec::new(),
 63                syntax: syntax_theme,
 64            },
 65        })
 66    }
 67
 68    fn convert_status_colors(&self) -> Result<StatusColorsContent> {
 69        let vscode_colors = &self.theme.colors;
 70
 71        let vscode_base_status_colors = StatusColorsContent {
 72            hint: Some("#969696ff".to_string()),
 73            ..Default::default()
 74        };
 75
 76        Ok(StatusColorsContent {
 77            conflict: vscode_colors
 78                .git_decoration
 79                .conflicting_resource_foreground
 80                .clone(),
 81            created: vscode_colors.editor_gutter.added_background.clone(),
 82            deleted: vscode_colors.editor_gutter.deleted_background.clone(),
 83            error: vscode_colors.editor_error.foreground.clone(),
 84            error_background: vscode_colors.editor_error.background.clone(),
 85            error_border: vscode_colors.editor_error.border.clone(),
 86            hidden: vscode_colors.tab.inactive_foreground.clone(),
 87            hint: vscode_colors
 88                .editor_inlay_hint
 89                .foreground
 90                .clone()
 91                .or(vscode_base_status_colors.hint),
 92            hint_border: vscode_colors.editor_hint.border.clone(),
 93            ignored: vscode_colors
 94                .git_decoration
 95                .ignored_resource_foreground
 96                .clone(),
 97            info: vscode_colors.editor_info.foreground.clone(),
 98            info_background: vscode_colors.editor_info.background.clone(),
 99            info_border: vscode_colors.editor_info.border.clone(),
100            modified: vscode_colors.editor_gutter.modified_background.clone(),
101            // renamed: None,
102            // success: None,
103            warning: vscode_colors.editor_warning.foreground.clone(),
104            warning_background: vscode_colors.editor_warning.background.clone(),
105            warning_border: vscode_colors.editor_warning.border.clone(),
106            ..Default::default()
107        })
108    }
109
110    fn convert_theme_colors(&self) -> Result<ThemeColorsContent> {
111        let vscode_colors = &self.theme.colors;
112
113        let vscode_panel_border = vscode_colors.panel.border.clone();
114        let vscode_tab_inactive_background = vscode_colors.tab.inactive_background.clone();
115        let vscode_editor_foreground = vscode_colors.editor.foreground.clone();
116        let vscode_editor_background = vscode_colors.editor.background.clone();
117        let vscode_scrollbar_slider_background = vscode_colors.scrollbar_slider.background.clone();
118        let vscode_token_colors_foreground = self
119            .theme
120            .token_colors
121            .iter()
122            .find(|token_color| token_color.scope.is_none())
123            .and_then(|token_color| token_color.settings.foreground.as_ref())
124            .cloned();
125
126        Ok(ThemeColorsContent {
127            border: vscode_panel_border.clone(),
128            border_variant: vscode_panel_border.clone(),
129            border_focused: vscode_colors.focus_border.clone(),
130            border_selected: vscode_panel_border.clone(),
131            border_transparent: vscode_panel_border.clone(),
132            border_disabled: vscode_panel_border.clone(),
133            elevated_surface_background: vscode_colors.dropdown.background.clone(),
134            surface_background: vscode_colors.panel.background.clone(),
135            background: vscode_editor_background.clone(),
136            element_background: vscode_colors.button.background.clone(),
137            element_hover: vscode_colors.list.hover_background.clone(),
138            element_selected: vscode_colors.list.active_selection_background.clone(),
139            drop_target_background: vscode_colors.list.drop_background.clone(),
140            ghost_element_hover: vscode_colors.list.hover_background.clone(),
141            ghost_element_selected: vscode_colors.list.active_selection_background.clone(),
142            text: vscode_colors
143                .foreground
144                .clone()
145                .or(vscode_token_colors_foreground.clone()),
146            text_muted: vscode_colors.tab.inactive_foreground.clone(),
147            status_bar_background: vscode_colors.status_bar.background.clone(),
148            title_bar_background: vscode_colors.title_bar.active_background.clone(),
149            toolbar_background: vscode_colors
150                .breadcrumb
151                .background
152                .clone()
153                .or(vscode_editor_background.clone()),
154            tab_bar_background: vscode_colors.editor_group_header.tabs_background.clone(),
155            tab_inactive_background: vscode_tab_inactive_background.clone(),
156            tab_active_background: vscode_colors
157                .tab
158                .active_background
159                .clone()
160                .or(vscode_tab_inactive_background.clone()),
161            panel_background: vscode_colors.panel.background.clone(),
162            scrollbar_thumb_background: vscode_scrollbar_slider_background.clone(),
163            scrollbar_thumb_hover_background: vscode_colors
164                .scrollbar_slider
165                .hover_background
166                .clone(),
167            scrollbar_thumb_border: vscode_scrollbar_slider_background.clone(),
168            scrollbar_track_background: vscode_editor_background.clone(),
169            scrollbar_track_border: vscode_colors.editor_overview_ruler.border.clone(),
170            pane_group_border: vscode_colors.editor_group.border.clone(),
171            editor_foreground: vscode_editor_foreground
172                .clone()
173                .or(vscode_token_colors_foreground.clone()),
174            editor_background: vscode_editor_background.clone(),
175            editor_gutter_background: vscode_editor_background.clone(),
176            editor_active_line_background: vscode_colors.editor.line_highlight_background.clone(),
177            editor_line_number: vscode_colors.editor_line_number.foreground.clone(),
178            editor_active_line_number: vscode_colors.editor.foreground.clone(),
179            editor_wrap_guide: vscode_panel_border.clone(),
180            editor_active_wrap_guide: vscode_panel_border.clone(),
181            terminal_background: vscode_colors.terminal.background.clone(),
182            terminal_ansi_black: vscode_colors.terminal.ansi_black.clone(),
183            terminal_ansi_bright_black: vscode_colors.terminal.ansi_bright_black.clone(),
184            terminal_ansi_red: vscode_colors.terminal.ansi_red.clone(),
185            terminal_ansi_bright_red: vscode_colors.terminal.ansi_bright_red.clone(),
186            terminal_ansi_green: vscode_colors.terminal.ansi_green.clone(),
187            terminal_ansi_bright_green: vscode_colors.terminal.ansi_bright_green.clone(),
188            terminal_ansi_yellow: vscode_colors.terminal.ansi_yellow.clone(),
189            terminal_ansi_bright_yellow: vscode_colors.terminal.ansi_bright_yellow.clone(),
190            terminal_ansi_blue: vscode_colors.terminal.ansi_blue.clone(),
191            terminal_ansi_bright_blue: vscode_colors.terminal.ansi_bright_blue.clone(),
192            terminal_ansi_magenta: vscode_colors.terminal.ansi_magenta.clone(),
193            terminal_ansi_bright_magenta: vscode_colors.terminal.ansi_bright_magenta.clone(),
194            terminal_ansi_cyan: vscode_colors.terminal.ansi_cyan.clone(),
195            terminal_ansi_bright_cyan: vscode_colors.terminal.ansi_bright_cyan.clone(),
196            terminal_ansi_white: vscode_colors.terminal.ansi_white.clone(),
197            terminal_ansi_bright_white: vscode_colors.terminal.ansi_bright_white.clone(),
198            link_text_hover: vscode_colors.text_link.active_foreground.clone(),
199            ..Default::default()
200        })
201    }
202
203    fn convert_syntax_theme(&self) -> Result<IndexMap<String, HighlightStyleContent>> {
204        let mut highlight_styles = IndexMap::new();
205
206        for syntax_token in ZedSyntaxToken::iter() {
207            let override_match = self
208                .syntax_overrides
209                .get(&syntax_token.to_string())
210                .and_then(|scope| {
211                    self.theme.token_colors.iter().find(|token_color| {
212                        token_color.scope == Some(VsCodeTokenScope::Many(scope.clone()))
213                    })
214                });
215
216            let best_match = override_match
217                .or_else(|| syntax_token.find_best_token_color_match(&self.theme.token_colors))
218                .or_else(|| {
219                    syntax_token.fallbacks().iter().find_map(|fallback| {
220                        fallback.find_best_token_color_match(&self.theme.token_colors)
221                    })
222                });
223
224            let Some(token_color) = best_match else {
225                log::warn!("No matching token color found for '{syntax_token}'");
226                continue;
227            };
228
229            log::info!(
230                "Matched '{syntax_token}' to '{}'",
231                token_color
232                    .name
233                    .clone()
234                    .or_else(|| token_color
235                        .scope
236                        .as_ref()
237                        .map(|scope| format!("{:?}", scope)))
238                    .unwrap_or_else(|| "no identifier".to_string())
239            );
240
241            let highlight_style = HighlightStyleContent {
242                color: token_color.settings.foreground.clone(),
243                font_style: token_color
244                    .settings
245                    .font_style
246                    .as_ref()
247                    .and_then(|style| try_parse_font_style(&style)),
248                font_weight: token_color
249                    .settings
250                    .font_style
251                    .as_ref()
252                    .and_then(|style| try_parse_font_weight(&style)),
253            };
254
255            if highlight_style.is_empty() {
256                continue;
257            }
258
259            highlight_styles.insert(syntax_token.to_string(), highlight_style);
260        }
261
262        Ok(highlight_styles)
263    }
264}