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_conflicting_resource_foreground
78 .clone(),
79 created: vscode_colors.editor_gutter_added_background.clone(),
80 deleted: vscode_colors.editor_gutter_deleted_background.clone(),
81 error: vscode_colors.editor_error_foreground.clone(),
82 error_background: vscode_colors.editor_error_background.clone(),
83 error_border: vscode_colors.editor_error_border.clone(),
84 hidden: vscode_colors.tab_inactive_foreground.clone(),
85 hint: vscode_colors
86 .editor_inlay_hint_foreground
87 .clone()
88 .or(vscode_base_status_colors.hint),
89 hint_border: vscode_colors.editor_hint_border.clone(),
90 ignored: vscode_colors
91 .git_decoration_ignored_resource_foreground
92 .clone(),
93 info: vscode_colors.editor_info_foreground.clone(),
94 info_background: vscode_colors.editor_info_background.clone(),
95 info_border: vscode_colors.editor_info_border.clone(),
96 modified: vscode_colors.editor_gutter_modified_background.clone(),
97 // renamed: None,
98 // success: None,
99 warning: vscode_colors.editor_warning_foreground.clone(),
100 warning_background: vscode_colors.editor_warning_background.clone(),
101 warning_border: vscode_colors.editor_warning_border.clone(),
102 ..Default::default()
103 })
104 }
105
106 fn convert_theme_colors(&self) -> Result<ThemeColorsContent> {
107 let vscode_colors = &self.theme.colors;
108
109 let vscode_panel_border = vscode_colors.panel_border.clone();
110 let vscode_tab_inactive_background = vscode_colors.tab_inactive_background.clone();
111 let vscode_editor_foreground = vscode_colors.editor_foreground.clone();
112 let vscode_editor_background = vscode_colors.editor_background.clone();
113 let vscode_scrollbar_slider_background = vscode_colors.scrollbar_slider_background.clone();
114 let vscode_token_colors_foreground = self
115 .theme
116 .token_colors
117 .iter()
118 .find(|token_color| token_color.scope.is_none())
119 .and_then(|token_color| token_color.settings.foreground.as_ref())
120 .cloned();
121
122 Ok(ThemeColorsContent {
123 border: vscode_panel_border.clone(),
124 border_variant: vscode_panel_border.clone(),
125 border_focused: vscode_colors.focus_border.clone(),
126 border_selected: vscode_panel_border.clone(),
127 border_transparent: vscode_panel_border.clone(),
128 border_disabled: vscode_panel_border.clone(),
129 elevated_surface_background: vscode_colors.dropdown_background.clone(),
130 surface_background: vscode_colors.panel_background.clone(),
131 background: vscode_editor_background.clone(),
132 element_background: vscode_colors.button_background.clone(),
133 element_hover: vscode_colors.list_hover_background.clone(),
134 element_selected: vscode_colors.list_active_selection_background.clone(),
135 drop_target_background: vscode_colors.list_drop_background.clone(),
136 ghost_element_hover: vscode_colors.list_hover_background.clone(),
137 ghost_element_selected: vscode_colors.list_active_selection_background.clone(),
138 text: vscode_colors
139 .foreground
140 .clone()
141 .or(vscode_token_colors_foreground.clone()),
142 text_muted: vscode_colors.tab_inactive_foreground.clone(),
143 status_bar_background: vscode_colors.status_bar_background.clone(),
144 title_bar_background: vscode_colors.title_bar_active_background.clone(),
145 toolbar_background: vscode_colors
146 .breadcrumb_background
147 .clone()
148 .or(vscode_editor_background.clone()),
149 tab_bar_background: vscode_colors.editor_group_header_tabs_background.clone(),
150 tab_inactive_background: vscode_tab_inactive_background.clone(),
151 tab_active_background: vscode_colors
152 .tab_active_background
153 .clone()
154 .or(vscode_tab_inactive_background.clone()),
155 panel_background: vscode_colors.panel_background.clone(),
156 scrollbar_thumb_background: vscode_scrollbar_slider_background.clone(),
157 scrollbar_thumb_hover_background: vscode_colors
158 .scrollbar_slider_hover_background
159 .clone(),
160 scrollbar_thumb_border: vscode_scrollbar_slider_background.clone(),
161 scrollbar_track_background: vscode_editor_background.clone(),
162 scrollbar_track_border: vscode_colors.editor_overview_ruler_border.clone(),
163 editor_foreground: vscode_editor_foreground
164 .clone()
165 .or(vscode_token_colors_foreground.clone()),
166 editor_background: vscode_editor_background.clone(),
167 editor_gutter_background: vscode_editor_background.clone(),
168 editor_active_line_background: vscode_colors.editor_line_highlight_background.clone(),
169 editor_line_number: vscode_colors.editor_line_number_foreground.clone(),
170 editor_active_line_number: vscode_colors.editor_foreground.clone(),
171 editor_wrap_guide: vscode_panel_border.clone(),
172 editor_active_wrap_guide: vscode_panel_border.clone(),
173 terminal_background: vscode_colors.terminal_background.clone(),
174 terminal_ansi_black: vscode_colors.terminal_ansi_black.clone(),
175 terminal_ansi_bright_black: vscode_colors.terminal_ansi_bright_black.clone(),
176 terminal_ansi_red: vscode_colors.terminal_ansi_red.clone(),
177 terminal_ansi_bright_red: vscode_colors.terminal_ansi_bright_red.clone(),
178 terminal_ansi_green: vscode_colors.terminal_ansi_green.clone(),
179 terminal_ansi_bright_green: vscode_colors.terminal_ansi_bright_green.clone(),
180 terminal_ansi_yellow: vscode_colors.terminal_ansi_yellow.clone(),
181 terminal_ansi_bright_yellow: vscode_colors.terminal_ansi_bright_yellow.clone(),
182 terminal_ansi_blue: vscode_colors.terminal_ansi_blue.clone(),
183 terminal_ansi_bright_blue: vscode_colors.terminal_ansi_bright_blue.clone(),
184 terminal_ansi_magenta: vscode_colors.terminal_ansi_magenta.clone(),
185 terminal_ansi_bright_magenta: vscode_colors.terminal_ansi_bright_magenta.clone(),
186 terminal_ansi_cyan: vscode_colors.terminal_ansi_cyan.clone(),
187 terminal_ansi_bright_cyan: vscode_colors.terminal_ansi_bright_cyan.clone(),
188 terminal_ansi_white: vscode_colors.terminal_ansi_white.clone(),
189 terminal_ansi_bright_white: vscode_colors.terminal_ansi_bright_white.clone(),
190 link_text_hover: vscode_colors.text_link_active_foreground.clone(),
191 ..Default::default()
192 })
193 }
194
195 fn convert_syntax_theme(&self) -> Result<IndexMap<String, HighlightStyleContent>> {
196 let mut highlight_styles = IndexMap::new();
197
198 for syntax_token in ZedSyntaxToken::iter() {
199 let override_match = self
200 .syntax_overrides
201 .get(&syntax_token.to_string())
202 .and_then(|scope| {
203 self.theme.token_colors.iter().find(|token_color| {
204 token_color.scope == Some(VsCodeTokenScope::Many(scope.clone()))
205 })
206 });
207
208 let best_match = override_match
209 .or_else(|| syntax_token.find_best_token_color_match(&self.theme.token_colors))
210 .or_else(|| {
211 syntax_token.fallbacks().iter().find_map(|fallback| {
212 fallback.find_best_token_color_match(&self.theme.token_colors)
213 })
214 });
215
216 let Some(token_color) = best_match else {
217 log::warn!("No matching token color found for '{syntax_token}'");
218 continue;
219 };
220
221 log::info!(
222 "Matched '{syntax_token}' to '{}'",
223 token_color
224 .name
225 .clone()
226 .or_else(|| token_color
227 .scope
228 .as_ref()
229 .map(|scope| format!("{:?}", scope)))
230 .unwrap_or_else(|| "no identifier".to_string())
231 );
232
233 let highlight_style = HighlightStyleContent {
234 color: token_color.settings.foreground.clone(),
235 font_style: token_color
236 .settings
237 .font_style
238 .as_ref()
239 .and_then(|style| try_parse_font_style(&style)),
240 font_weight: token_color
241 .settings
242 .font_style
243 .as_ref()
244 .and_then(|style| try_parse_font_weight(&style)),
245 };
246
247 if highlight_style.is_empty() {
248 continue;
249 }
250
251 highlight_styles.insert(syntax_token.to_string(), highlight_style);
252 }
253
254 Ok(highlight_styles)
255 }
256}