converter.rs

  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}