1use anyhow::{Context, Result};
2use gpui::{Hsla, Rgba};
3use theme::{
4 color_alpha, Appearance, PlayerColor, PlayerColors, StatusColorsRefinement,
5 ThemeColorsRefinement, UserFontStyle, UserFontWeight, UserHighlightStyle, UserSyntaxTheme,
6 UserTheme, UserThemeStylesRefinement,
7};
8
9use crate::zed1::theme::{
10 Color as Zed1Color, ColorScheme, HighlightStyle as Zed1HighlightStyle, Theme as Zed1Theme,
11 Weight,
12};
13
14fn zed1_color_to_hsla(color: Zed1Color) -> Hsla {
15 let r = color.r as f32 / 255.;
16 let g = color.g as f32 / 255.;
17 let b = color.b as f32 / 255.;
18 let a = color.a as f32 / 255.;
19
20 Hsla::from(Rgba { r, g, b, a })
21}
22
23fn zed1_highlight_style_to_user_highlight_style(
24 highlight: Zed1HighlightStyle,
25) -> UserHighlightStyle {
26 UserHighlightStyle {
27 color: highlight.color.map(zed1_color_to_hsla),
28 font_style: highlight.italic.map(|is_italic| {
29 if is_italic {
30 UserFontStyle::Italic
31 } else {
32 UserFontStyle::Normal
33 }
34 }),
35 font_weight: highlight.weight.map(|weight| match weight {
36 Weight::thin => UserFontWeight::THIN,
37 Weight::extra_light => UserFontWeight::EXTRA_LIGHT,
38 Weight::light => UserFontWeight::LIGHT,
39 Weight::normal => UserFontWeight::NORMAL,
40 Weight::medium => UserFontWeight::MEDIUM,
41 Weight::semibold => UserFontWeight::SEMIBOLD,
42 Weight::bold => UserFontWeight::BOLD,
43 Weight::extra_bold => UserFontWeight::EXTRA_BOLD,
44 Weight::black => UserFontWeight::BLACK,
45 }),
46 }
47}
48
49pub struct Zed1ThemeConverter {
50 theme: Zed1Theme,
51}
52
53impl Zed1ThemeConverter {
54 pub fn new(theme: Zed1Theme) -> Self {
55 Self { theme }
56 }
57
58 pub fn convert(self) -> Result<UserTheme> {
59 let appearance = match self.theme.meta.is_light {
60 true => Appearance::Light,
61 false => Appearance::Dark,
62 };
63
64 let status_colors_refinement = self.convert_status_colors()?;
65 let theme_colors_refinement = self.convert_theme_colors()?;
66 let player_colors = self.convert_player_colors()?;
67 let syntax_theme = self.convert_syntax_theme()?;
68
69 Ok(UserTheme {
70 name: self.theme.meta.name,
71 appearance,
72 styles: UserThemeStylesRefinement {
73 colors: theme_colors_refinement,
74 status: status_colors_refinement,
75 player: Some(player_colors),
76 syntax: Some(syntax_theme),
77 },
78 })
79 }
80
81 fn convert_status_colors(&self) -> Result<StatusColorsRefinement> {
82 fn convert(color: Zed1Color) -> Option<Hsla> {
83 Some(zed1_color_to_hsla(color))
84 }
85
86 let base_theme: ColorScheme = serde_json::from_value(self.theme.base_theme.clone())
87 .with_context(|| "failed to parse `theme.base_theme`")?;
88
89 let lowest = &base_theme.lowest;
90
91 let editor = &self.theme.editor;
92
93 Ok(StatusColorsRefinement {
94 created: convert(lowest.positive.default.foreground),
95 created_background: convert(lowest.positive.default.background),
96 created_border: convert(lowest.positive.default.border),
97 modified: convert(lowest.warning.default.foreground),
98 modified_background: convert(lowest.warning.default.background),
99 modified_border: convert(lowest.warning.default.border),
100 deleted: convert(lowest.negative.default.foreground),
101 deleted_background: convert(lowest.negative.default.background),
102 deleted_border: convert(lowest.negative.default.border),
103 success: convert(lowest.positive.default.foreground),
104 success_background: convert(lowest.positive.default.background),
105 success_border: convert(lowest.positive.default.border),
106 warning: convert(lowest.warning.default.foreground),
107 warning_background: convert(lowest.warning.default.background),
108 warning_border: convert(lowest.warning.default.border),
109 error: convert(lowest.negative.default.foreground),
110 error_background: convert(lowest.negative.default.background),
111 error_border: convert(lowest.negative.default.border),
112 // The `hint` color used in Zed1 is inlined from the syntax colors.
113 hint: editor
114 .hint
115 .color
116 .map(zed1_color_to_hsla)
117 .or(convert(lowest.accent.default.foreground)),
118 hint_background: convert(lowest.accent.default.background),
119 hint_border: convert(lowest.accent.default.border),
120 predictive: editor
121 .suggestion
122 .color
123 .map(zed1_color_to_hsla)
124 .or(convert(lowest.positive.default.foreground)),
125 predictive_background: convert(lowest.positive.default.background),
126 predictive_border: convert(lowest.positive.default.border),
127 conflict: convert(lowest.warning.default.foreground),
128 conflict_background: convert(lowest.warning.default.background),
129 conflict_border: convert(lowest.warning.default.border),
130 hidden: convert(lowest.base.disabled.foreground),
131 hidden_background: convert(lowest.base.disabled.background),
132 hidden_border: convert(lowest.base.disabled.border),
133 ignored: convert(lowest.variant.default.foreground),
134 ignored_background: convert(lowest.variant.default.background),
135 ignored_border: convert(lowest.variant.default.border),
136 info: convert(lowest.accent.default.foreground),
137 info_background: convert(lowest.accent.default.background),
138 info_border: convert(lowest.accent.default.border),
139 renamed: convert(lowest.accent.default.foreground),
140 renamed_background: convert(lowest.accent.default.background),
141 renamed_border: convert(lowest.accent.default.border),
142 unreachable: convert(lowest.variant.default.foreground), // TODO: Should this be transparent?
143 unreachable_background: convert(lowest.variant.default.background),
144 unreachable_border: convert(lowest.variant.default.border),
145 })
146 }
147
148 fn convert_player_colors(&self) -> Result<PlayerColors> {
149 let player_one = self.theme.editor.selection;
150
151 let mut player_colors = vec![PlayerColor {
152 cursor: zed1_color_to_hsla(player_one.cursor),
153 selection: zed1_color_to_hsla(player_one.selection),
154 background: zed1_color_to_hsla(player_one.cursor),
155 }];
156
157 for index in 1..8 {
158 let player = self
159 .theme
160 .editor
161 .selection_style_for_room_participant(index);
162
163 player_colors.push(PlayerColor {
164 cursor: zed1_color_to_hsla(player.cursor),
165 selection: zed1_color_to_hsla(player.selection),
166 background: zed1_color_to_hsla(player.cursor),
167 });
168 }
169
170 Ok(PlayerColors(player_colors))
171 }
172
173 fn convert_theme_colors(&self) -> Result<ThemeColorsRefinement> {
174 fn convert(color: Zed1Color) -> Option<Hsla> {
175 Some(zed1_color_to_hsla(color))
176 }
177
178 let base_theme: ColorScheme = serde_json::from_value(self.theme.base_theme.clone())
179 .with_context(|| "failed to parse `theme.base_theme`")?;
180
181 let lowest = &base_theme.lowest;
182 let middle = &base_theme.middle;
183 let highest = &base_theme.highest;
184
185 let editor = &self.theme.editor;
186 let terminal = &self.theme.terminal;
187
188 Ok(ThemeColorsRefinement {
189 border: convert(lowest.base.default.border),
190 border_variant: convert(middle.variant.default.border),
191 border_focused: convert(lowest.accent.hovered.border),
192 border_selected: convert(lowest.accent.default.border),
193 border_transparent: Some(gpui::transparent_black()),
194 border_disabled: convert(lowest.base.disabled.border),
195 elevated_surface_background: convert(middle.base.default.background),
196 surface_background: convert(middle.base.default.background),
197 background: convert(lowest.base.default.background),
198 element_background: convert(lowest.on.default.background),
199 element_hover: convert(lowest.on.hovered.background),
200 element_active: convert(lowest.on.active.background),
201 element_selected: convert(lowest.on.active.background), // TODO: Check what this should be
202 element_disabled: convert(lowest.on.disabled.background),
203 drop_target_background: convert(self.theme.workspace.drop_target_overlay_color),
204 ghost_element_background: Some(gpui::transparent_black()),
205 ghost_element_hover: convert(lowest.on.hovered.background),
206 ghost_element_active: convert(lowest.on.active.background),
207 ghost_element_selected: convert(lowest.on.active.background), // TODO: Check what this should be
208 ghost_element_disabled: convert(lowest.on.disabled.background),
209 text: convert(lowest.base.default.foreground),
210 text_muted: convert(lowest.variant.default.foreground),
211 text_placeholder: convert(lowest.base.disabled.foreground), // TODO: What should placeholder be?
212 text_disabled: convert(lowest.base.disabled.foreground),
213 text_accent: convert(lowest.accent.default.foreground),
214 icon: convert(lowest.base.default.foreground),
215 icon_muted: convert(lowest.variant.default.foreground),
216 icon_disabled: convert(lowest.base.disabled.foreground),
217 icon_placeholder: convert(lowest.variant.default.foreground),
218 icon_accent: convert(lowest.accent.default.foreground),
219 status_bar_background: convert(lowest.base.default.background),
220 title_bar_background: convert(lowest.base.default.background),
221 toolbar_background: convert(highest.base.default.background),
222 tab_bar_background: convert(middle.base.default.background),
223 tab_inactive_background: convert(middle.base.default.background),
224 tab_active_background: convert(highest.base.default.background),
225 search_match_background: convert(highest.accent.default.background),
226 panel_background: convert(middle.base.default.background),
227 panel_focused_border: convert(lowest.accent.hovered.border),
228 pane_focused_border: convert(lowest.accent.hovered.border),
229 scrollbar_thumb_background: convert(middle.base.inverted.background)
230 .map(|color| color_alpha(color, 0.3)),
231 scrollbar_thumb_hover_background: convert(middle.base.hovered.background),
232 scrollbar_thumb_border: convert(middle.base.default.border),
233 scrollbar_track_background: Some(gpui::transparent_black()),
234 scrollbar_track_border: convert(highest.variant.default.border),
235 editor_foreground: convert(editor.text_color),
236 editor_background: convert(editor.background),
237 editor_gutter_background: convert(editor.gutter_background),
238 editor_subheader_background: convert(middle.base.default.background),
239 editor_active_line_background: convert(editor.active_line_background),
240 editor_highlighted_line_background: convert(editor.highlighted_line_background),
241 editor_line_number: convert(editor.line_number),
242 editor_active_line_number: convert(editor.line_number_active),
243 editor_invisible: convert(editor.whitespace),
244 editor_wrap_guide: convert(editor.wrap_guide),
245 editor_active_wrap_guide: convert(editor.active_wrap_guide),
246 editor_document_highlight_read_background: convert(
247 editor.document_highlight_read_background,
248 ),
249 editor_document_highlight_write_background: convert(
250 editor.document_highlight_write_background,
251 ),
252 terminal_background: convert(terminal.background),
253 terminal_foreground: convert(terminal.foreground),
254 terminal_bright_foreground: convert(terminal.bright_foreground),
255 terminal_dim_foreground: convert(terminal.dim_foreground),
256 terminal_ansi_black: convert(terminal.black),
257 terminal_ansi_bright_black: convert(terminal.bright_black),
258 terminal_ansi_dim_black: convert(terminal.dim_black),
259 terminal_ansi_red: convert(terminal.red),
260 terminal_ansi_bright_red: convert(terminal.bright_red),
261 terminal_ansi_dim_red: convert(terminal.dim_red),
262 terminal_ansi_green: convert(terminal.green),
263 terminal_ansi_bright_green: convert(terminal.bright_green),
264 terminal_ansi_dim_green: convert(terminal.dim_green),
265 terminal_ansi_yellow: convert(terminal.yellow),
266 terminal_ansi_bright_yellow: convert(terminal.bright_yellow),
267 terminal_ansi_dim_yellow: convert(terminal.dim_yellow),
268 terminal_ansi_blue: convert(terminal.blue),
269 terminal_ansi_bright_blue: convert(terminal.bright_blue),
270 terminal_ansi_dim_blue: convert(terminal.dim_blue),
271 terminal_ansi_magenta: convert(terminal.magenta),
272 terminal_ansi_bright_magenta: convert(terminal.bright_magenta),
273 terminal_ansi_dim_magenta: convert(terminal.dim_magenta),
274 terminal_ansi_cyan: convert(terminal.cyan),
275 terminal_ansi_bright_cyan: convert(terminal.bright_cyan),
276 terminal_ansi_dim_cyan: convert(terminal.dim_cyan),
277 terminal_ansi_white: convert(terminal.white),
278 terminal_ansi_bright_white: convert(terminal.bright_white),
279 terminal_ansi_dim_white: convert(terminal.dim_white),
280 link_text_hover: convert(highest.accent.default.foreground),
281 })
282 }
283
284 fn convert_syntax_theme(&self) -> Result<UserSyntaxTheme> {
285 Ok(UserSyntaxTheme {
286 highlights: self
287 .theme
288 .editor
289 .syntax
290 .highlights
291 .clone()
292 .into_iter()
293 .map(|(name, highlight_style)| {
294 (
295 name,
296 zed1_highlight_style_to_user_highlight_style(highlight_style),
297 )
298 })
299 .collect(),
300 })
301 }
302}