converter.rs

  1use anyhow::{Context, Result};
  2use gpui::{serde_json, 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: convert(lowest.positive.default.foreground),
121            predictive_background: convert(lowest.positive.default.background),
122            predictive_border: convert(lowest.positive.default.border),
123            conflict: convert(lowest.warning.default.foreground),
124            conflict_background: convert(lowest.warning.default.background),
125            conflict_border: convert(lowest.warning.default.border),
126            hidden: convert(lowest.base.disabled.foreground),
127            hidden_background: convert(lowest.base.disabled.background),
128            hidden_border: convert(lowest.base.disabled.border),
129            ignored: convert(lowest.variant.default.foreground),
130            ignored_background: convert(lowest.variant.default.background),
131            ignored_border: convert(lowest.variant.default.border),
132            info: convert(lowest.accent.default.foreground),
133            info_background: convert(lowest.accent.default.background),
134            info_border: convert(lowest.accent.default.border),
135            renamed: convert(lowest.accent.default.foreground),
136            renamed_background: convert(lowest.accent.default.background),
137            renamed_border: convert(lowest.accent.default.border),
138            unreachable: convert(lowest.variant.default.foreground), // TODO: Should this be transparent?
139            unreachable_background: convert(lowest.variant.default.background),
140            unreachable_border: convert(lowest.variant.default.border),
141        })
142    }
143
144    fn convert_player_colors(&self) -> Result<PlayerColors> {
145        let player_one = self.theme.editor.selection;
146
147        let mut player_colors = vec![PlayerColor {
148            cursor: zed1_color_to_hsla(player_one.cursor),
149            selection: zed1_color_to_hsla(player_one.selection),
150            background: zed1_color_to_hsla(player_one.cursor),
151        }];
152
153        for index in 1..8 {
154            let player = self
155                .theme
156                .editor
157                .selection_style_for_room_participant(index);
158
159            player_colors.push(PlayerColor {
160                cursor: zed1_color_to_hsla(player.cursor),
161                selection: zed1_color_to_hsla(player.selection),
162                background: zed1_color_to_hsla(player.cursor),
163            });
164        }
165
166        Ok(PlayerColors(player_colors))
167    }
168
169    fn convert_theme_colors(&self) -> Result<ThemeColorsRefinement> {
170        fn convert(color: Zed1Color) -> Option<Hsla> {
171            Some(zed1_color_to_hsla(color))
172        }
173
174        let base_theme: ColorScheme = serde_json::from_value(self.theme.base_theme.clone())
175            .with_context(|| "failed to parse `theme.base_theme`")?;
176
177        let lowest = &base_theme.lowest;
178        let middle = &base_theme.middle;
179        let highest = &base_theme.highest;
180
181        let editor = &self.theme.editor;
182        let terminal = &self.theme.terminal;
183
184        Ok(ThemeColorsRefinement {
185            border: convert(lowest.base.default.border),
186            border_variant: convert(lowest.variant.default.border),
187            border_focused: convert(lowest.accent.hovered.border),
188            border_selected: convert(lowest.accent.default.border),
189            border_transparent: Some(gpui::transparent_black()),
190            border_disabled: convert(lowest.base.disabled.border),
191            elevated_surface_background: convert(middle.base.default.background),
192            surface_background: convert(middle.base.default.background),
193            background: convert(lowest.base.default.background),
194            element_background: convert(lowest.on.default.background),
195            element_hover: convert(lowest.on.hovered.background),
196            element_active: convert(lowest.on.active.background),
197            element_selected: convert(lowest.on.active.background), // TODO: Check what this should be
198            element_disabled: convert(lowest.on.disabled.background),
199            drop_target_background: convert(self.theme.workspace.drop_target_overlay_color),
200            ghost_element_background: Some(gpui::transparent_black()),
201            ghost_element_hover: convert(lowest.on.hovered.background),
202            ghost_element_active: convert(lowest.on.active.background),
203            ghost_element_selected: convert(lowest.on.active.background), // TODO: Check what this should be
204            ghost_element_disabled: convert(lowest.on.disabled.background),
205            text: convert(lowest.base.default.foreground),
206            text_muted: convert(lowest.variant.default.foreground),
207            text_placeholder: convert(lowest.base.disabled.foreground), // TODO: What should placeholder be?
208            text_disabled: convert(lowest.base.disabled.foreground),
209            text_accent: convert(lowest.accent.default.foreground),
210            icon: convert(lowest.base.default.foreground),
211            icon_muted: convert(lowest.variant.default.foreground),
212            icon_disabled: convert(lowest.base.disabled.foreground),
213            icon_placeholder: convert(lowest.variant.default.foreground),
214            icon_accent: convert(lowest.accent.default.foreground),
215            status_bar_background: convert(lowest.base.default.background),
216            title_bar_background: convert(lowest.base.default.background),
217            toolbar_background: convert(highest.base.default.background),
218            tab_bar_background: convert(middle.base.default.background),
219            tab_inactive_background: convert(middle.base.default.background),
220            tab_active_background: convert(highest.base.default.background),
221            search_match_background: convert(highest.accent.default.background),
222            panel_background: convert(middle.base.default.background),
223            panel_focused_border: convert(lowest.accent.hovered.border),
224            pane_focused_border: convert(lowest.accent.hovered.border),
225            scrollbar_thumb_background: convert(middle.base.inverted.background)
226                .map(|color| color_alpha(color, 0.3)),
227            scrollbar_thumb_hover_background: convert(middle.base.hovered.background),
228            scrollbar_thumb_border: convert(middle.base.default.border),
229            scrollbar_track_background: convert(highest.base.default.background),
230            scrollbar_track_border: convert(highest.variant.default.border),
231            editor_foreground: convert(editor.text_color),
232            editor_background: convert(editor.background),
233            editor_gutter_background: convert(editor.gutter_background),
234            editor_subheader_background: convert(middle.base.default.background),
235            editor_active_line_background: convert(editor.active_line_background),
236            editor_highlighted_line_background: convert(editor.highlighted_line_background),
237            editor_line_number: convert(editor.line_number),
238            editor_active_line_number: convert(editor.line_number_active),
239            editor_invisible: convert(highest.variant.default.foreground), // TODO: Is this light enough?
240            editor_wrap_guide: convert(editor.wrap_guide),
241            editor_active_wrap_guide: convert(editor.active_wrap_guide),
242            editor_document_highlight_read_background: convert(
243                editor.document_highlight_read_background,
244            ),
245            editor_document_highlight_write_background: convert(
246                editor.document_highlight_write_background,
247            ),
248            terminal_background: convert(terminal.background),
249            terminal_ansi_bright_black: convert(terminal.bright_black),
250            terminal_ansi_bright_red: convert(terminal.bright_red),
251            terminal_ansi_bright_green: convert(terminal.bright_green),
252            terminal_ansi_bright_yellow: convert(terminal.bright_yellow),
253            terminal_ansi_bright_blue: convert(terminal.bright_blue),
254            terminal_ansi_bright_magenta: convert(terminal.bright_magenta),
255            terminal_ansi_bright_cyan: convert(terminal.bright_cyan),
256            terminal_ansi_bright_white: convert(terminal.bright_white),
257            terminal_ansi_black: convert(terminal.black),
258            terminal_ansi_red: convert(terminal.red),
259            terminal_ansi_green: convert(terminal.green),
260            terminal_ansi_yellow: convert(terminal.yellow),
261            terminal_ansi_blue: convert(terminal.blue),
262            terminal_ansi_magenta: convert(terminal.magenta),
263            terminal_ansi_cyan: convert(terminal.cyan),
264            terminal_ansi_white: convert(terminal.white),
265            link_text_hover: convert(highest.accent.default.foreground),
266        })
267    }
268
269    fn convert_syntax_theme(&self) -> Result<UserSyntaxTheme> {
270        Ok(UserSyntaxTheme {
271            highlights: self
272                .theme
273                .editor
274                .syntax
275                .highlights
276                .clone()
277                .into_iter()
278                .map(|(name, highlight_style)| {
279                    (
280                        name,
281                        zed1_highlight_style_to_user_highlight_style(highlight_style),
282                    )
283                })
284                .collect(),
285        })
286    }
287}