1use anyhow::Result;
2use collections::IndexMap;
3use strum::IntoEnumIterator;
4use theme::{
5 FontStyleContent, FontWeightContent, HighlightStyleContent, StatusColorsContent,
6 ThemeColorsContent, ThemeContent, ThemeStyleContent, WindowBackgroundContent,
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(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::default();
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}