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