theme.rs

  1mod theme_registry;
  2
  3use gpui::{
  4    color::Color,
  5    elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, TooltipStyle},
  6    fonts::{HighlightStyle, TextStyle},
  7    Border, MouseState,
  8};
  9use serde::{de::DeserializeOwned, Deserialize};
 10use serde_json::Value;
 11use std::{collections::HashMap, sync::Arc};
 12
 13pub use theme_registry::*;
 14
 15#[derive(Deserialize, Default)]
 16pub struct Theme {
 17    #[serde(default)]
 18    pub meta: ThemeMeta,
 19    pub workspace: Workspace,
 20    pub context_menu: ContextMenu,
 21    pub contacts_popover: ContactsPopover,
 22    pub contact_list: ContactList,
 23    pub contact_finder: ContactFinder,
 24    pub project_panel: ProjectPanel,
 25    pub command_palette: CommandPalette,
 26    pub picker: Picker,
 27    pub editor: Editor,
 28    pub feedback: Feedback,
 29    pub search: Search,
 30    pub project_diagnostics: ProjectDiagnostics,
 31    pub breadcrumbs: ContainedText,
 32    pub shared_screen: ContainerStyle,
 33    pub contact_notification: ContactNotification,
 34    pub update_notification: UpdateNotification,
 35    pub simple_message_notification: MessageNotification,
 36    pub project_shared_notification: ProjectSharedNotification,
 37    pub incoming_call_notification: IncomingCallNotification,
 38    pub tooltip: TooltipStyle,
 39    pub terminal: TerminalStyle,
 40    pub color_scheme: ColorScheme,
 41}
 42
 43#[derive(Deserialize, Default, Clone)]
 44pub struct ThemeMeta {
 45    pub name: String,
 46    pub is_light: bool,
 47}
 48
 49#[derive(Deserialize, Default)]
 50pub struct Workspace {
 51    pub background: Color,
 52    pub titlebar: Titlebar,
 53    pub tab_bar: TabBar,
 54    pub pane_divider: Border,
 55    pub leader_border_opacity: f32,
 56    pub leader_border_width: f32,
 57    pub sidebar: Sidebar,
 58    pub status_bar: StatusBar,
 59    pub toolbar: Toolbar,
 60    pub disconnected_overlay: ContainedText,
 61    pub modal: ContainerStyle,
 62    pub notification: ContainerStyle,
 63    pub notifications: Notifications,
 64    pub joining_project_avatar: ImageStyle,
 65    pub joining_project_message: ContainedText,
 66    pub external_location_message: ContainedText,
 67    pub dock: Dock,
 68    pub drop_target_overlay_color: Color,
 69}
 70
 71#[derive(Clone, Deserialize, Default)]
 72pub struct Titlebar {
 73    #[serde(flatten)]
 74    pub container: ContainerStyle,
 75    pub height: f32,
 76    pub title: TextStyle,
 77    pub avatar_width: f32,
 78    pub avatar_margin: f32,
 79    pub avatar_ribbon: AvatarRibbon,
 80    pub offline_icon: OfflineIcon,
 81    pub avatar: ImageStyle,
 82    pub inactive_avatar: ImageStyle,
 83    pub sign_in_prompt: Interactive<ContainedText>,
 84    pub outdated_warning: ContainedText,
 85    pub share_button: Interactive<ContainedText>,
 86    pub call_control: Interactive<IconButton>,
 87    pub toggle_contacts_button: Interactive<IconButton>,
 88    pub toggle_contacts_badge: ContainerStyle,
 89}
 90
 91#[derive(Deserialize, Default)]
 92pub struct ContactsPopover {
 93    #[serde(flatten)]
 94    pub container: ContainerStyle,
 95    pub height: f32,
 96    pub width: f32,
 97    pub invite_row_height: f32,
 98    pub invite_row: Interactive<ContainedLabel>,
 99}
100
101#[derive(Deserialize, Default)]
102pub struct ContactList {
103    pub user_query_editor: FieldEditor,
104    pub user_query_editor_height: f32,
105    pub add_contact_button: IconButton,
106    pub header_row: Interactive<ContainedText>,
107    pub leave_call: Interactive<ContainedText>,
108    pub contact_row: Interactive<ContainerStyle>,
109    pub row_height: f32,
110    pub project_row: Interactive<ProjectRow>,
111    pub tree_branch: Interactive<TreeBranch>,
112    pub contact_avatar: ImageStyle,
113    pub contact_status_free: ContainerStyle,
114    pub contact_status_busy: ContainerStyle,
115    pub contact_username: ContainedText,
116    pub contact_button: Interactive<IconButton>,
117    pub contact_button_spacing: f32,
118    pub disabled_button: IconButton,
119    pub section_icon_size: f32,
120    pub calling_indicator: ContainedText,
121}
122
123#[derive(Deserialize, Default)]
124pub struct Feedback {
125    pub feedback_popover: FeedbackPopover,
126    pub feedback_editor: FieldEditor,
127}
128
129#[derive(Deserialize, Default)]
130pub struct FeedbackPopover {
131    #[serde(flatten)]
132    pub container: ContainerStyle,
133    pub height: f32,
134    pub width: f32,
135}
136
137#[derive(Deserialize, Default)]
138pub struct ProjectRow {
139    #[serde(flatten)]
140    pub container: ContainerStyle,
141    pub icon: Icon,
142    pub name: ContainedText,
143}
144
145#[derive(Deserialize, Default, Clone, Copy)]
146pub struct TreeBranch {
147    pub width: f32,
148    pub color: Color,
149}
150
151#[derive(Deserialize, Default)]
152pub struct ContactFinder {
153    pub picker: Picker,
154    pub row_height: f32,
155    pub contact_avatar: ImageStyle,
156    pub contact_username: ContainerStyle,
157    pub contact_button: IconButton,
158    pub disabled_contact_button: IconButton,
159}
160
161#[derive(Clone, Deserialize, Default)]
162pub struct TabBar {
163    #[serde(flatten)]
164    pub container: ContainerStyle,
165    pub pane_button: Interactive<IconButton>,
166    pub pane_button_container: ContainerStyle,
167    pub active_pane: TabStyles,
168    pub inactive_pane: TabStyles,
169    pub dragged_tab: Tab,
170    pub height: f32,
171}
172
173impl TabBar {
174    pub fn tab_style(&self, pane_active: bool, tab_active: bool) -> &Tab {
175        let tabs = if pane_active {
176            &self.active_pane
177        } else {
178            &self.inactive_pane
179        };
180
181        if tab_active {
182            &tabs.active_tab
183        } else {
184            &tabs.inactive_tab
185        }
186    }
187}
188
189#[derive(Clone, Deserialize, Default)]
190pub struct TabStyles {
191    pub active_tab: Tab,
192    pub inactive_tab: Tab,
193}
194
195#[derive(Clone, Deserialize, Default)]
196pub struct AvatarRibbon {
197    #[serde(flatten)]
198    pub container: ContainerStyle,
199    pub width: f32,
200    pub height: f32,
201}
202
203#[derive(Clone, Deserialize, Default)]
204pub struct OfflineIcon {
205    #[serde(flatten)]
206    pub container: ContainerStyle,
207    pub width: f32,
208    pub color: Color,
209}
210
211#[derive(Clone, Deserialize, Default)]
212pub struct Tab {
213    pub height: f32,
214    #[serde(flatten)]
215    pub container: ContainerStyle,
216    #[serde(flatten)]
217    pub label: LabelStyle,
218    pub description: ContainedText,
219    pub spacing: f32,
220    pub icon_width: f32,
221    pub icon_close: Color,
222    pub icon_close_active: Color,
223    pub icon_dirty: Color,
224    pub icon_conflict: Color,
225}
226
227#[derive(Clone, Deserialize, Default)]
228pub struct Toolbar {
229    #[serde(flatten)]
230    pub container: ContainerStyle,
231    pub height: f32,
232    pub item_spacing: f32,
233    pub nav_button: Interactive<IconButton>,
234}
235
236#[derive(Clone, Deserialize, Default)]
237pub struct Dock {
238    pub initial_size_right: f32,
239    pub initial_size_bottom: f32,
240    pub wash_color: Color,
241    pub panel: ContainerStyle,
242    pub maximized: ContainerStyle,
243}
244
245#[derive(Clone, Deserialize, Default)]
246pub struct Notifications {
247    #[serde(flatten)]
248    pub container: ContainerStyle,
249    pub width: f32,
250}
251
252#[derive(Clone, Deserialize, Default)]
253pub struct Search {
254    #[serde(flatten)]
255    pub container: ContainerStyle,
256    pub editor: FindEditor,
257    pub invalid_editor: ContainerStyle,
258    pub option_button_group: ContainerStyle,
259    pub option_button: Interactive<ContainedText>,
260    pub match_background: Color,
261    pub match_index: ContainedText,
262    pub results_status: TextStyle,
263    pub tab_icon_width: f32,
264    pub tab_icon_spacing: f32,
265    pub dismiss_button: Interactive<IconButton>,
266}
267
268#[derive(Clone, Deserialize, Default)]
269pub struct FindEditor {
270    #[serde(flatten)]
271    pub input: FieldEditor,
272    pub min_width: f32,
273    pub max_width: f32,
274}
275
276#[derive(Deserialize, Default)]
277pub struct StatusBar {
278    #[serde(flatten)]
279    pub container: ContainerStyle,
280    pub height: f32,
281    pub item_spacing: f32,
282    pub cursor_position: TextStyle,
283    pub auto_update_progress_message: TextStyle,
284    pub auto_update_done_message: TextStyle,
285    pub lsp_status: Interactive<StatusBarLspStatus>,
286    pub feedback: Interactive<TextStyle>,
287    pub sidebar_buttons: StatusBarSidebarButtons,
288    pub diagnostic_summary: Interactive<StatusBarDiagnosticSummary>,
289    pub diagnostic_message: Interactive<ContainedText>,
290}
291
292#[derive(Deserialize, Default)]
293pub struct StatusBarSidebarButtons {
294    pub group_left: ContainerStyle,
295    pub group_right: ContainerStyle,
296    pub item: Interactive<SidebarItem>,
297    pub badge: ContainerStyle,
298}
299
300#[derive(Deserialize, Default)]
301pub struct StatusBarDiagnosticSummary {
302    pub container_ok: ContainerStyle,
303    pub container_warning: ContainerStyle,
304    pub container_error: ContainerStyle,
305    pub text: TextStyle,
306    pub icon_color_ok: Color,
307    pub icon_color_warning: Color,
308    pub icon_color_error: Color,
309    pub height: f32,
310    pub icon_width: f32,
311    pub icon_spacing: f32,
312    pub summary_spacing: f32,
313}
314
315#[derive(Deserialize, Default)]
316pub struct StatusBarLspStatus {
317    #[serde(flatten)]
318    pub container: ContainerStyle,
319    pub height: f32,
320    pub icon_spacing: f32,
321    pub icon_color: Color,
322    pub icon_width: f32,
323    pub message: TextStyle,
324}
325
326#[derive(Deserialize, Default)]
327pub struct Sidebar {
328    pub initial_size: f32,
329    #[serde(flatten)]
330    pub container: ContainerStyle,
331}
332
333#[derive(Clone, Copy, Deserialize, Default)]
334pub struct SidebarItem {
335    #[serde(flatten)]
336    pub container: ContainerStyle,
337    pub icon_color: Color,
338    pub icon_size: f32,
339}
340
341#[derive(Deserialize, Default)]
342pub struct ProjectPanel {
343    #[serde(flatten)]
344    pub container: ContainerStyle,
345    pub entry: Interactive<ProjectPanelEntry>,
346    pub dragged_entry: ProjectPanelEntry,
347    pub ignored_entry: Interactive<ProjectPanelEntry>,
348    pub cut_entry: Interactive<ProjectPanelEntry>,
349    pub filename_editor: FieldEditor,
350    pub indent_width: f32,
351}
352
353#[derive(Clone, Debug, Deserialize, Default)]
354pub struct ProjectPanelEntry {
355    pub height: f32,
356    #[serde(flatten)]
357    pub container: ContainerStyle,
358    pub text: TextStyle,
359    pub icon_color: Color,
360    pub icon_size: f32,
361    pub icon_spacing: f32,
362}
363
364#[derive(Clone, Debug, Deserialize, Default)]
365pub struct ContextMenu {
366    #[serde(flatten)]
367    pub container: ContainerStyle,
368    pub item: Interactive<ContextMenuItem>,
369    pub keystroke_margin: f32,
370    pub separator: ContainerStyle,
371}
372
373#[derive(Clone, Debug, Deserialize, Default)]
374pub struct ContextMenuItem {
375    #[serde(flatten)]
376    pub container: ContainerStyle,
377    pub label: TextStyle,
378    pub keystroke: ContainedText,
379    pub icon_width: f32,
380    pub icon_spacing: f32,
381}
382
383#[derive(Debug, Deserialize, Default)]
384pub struct CommandPalette {
385    pub key: Interactive<ContainedLabel>,
386    pub keystroke_spacing: f32,
387}
388
389#[derive(Deserialize, Default)]
390pub struct InviteLink {
391    #[serde(flatten)]
392    pub container: ContainerStyle,
393    #[serde(flatten)]
394    pub label: LabelStyle,
395    pub icon: Icon,
396}
397
398#[derive(Deserialize, Default)]
399pub struct Icon {
400    #[serde(flatten)]
401    pub container: ContainerStyle,
402    pub color: Color,
403    pub width: f32,
404}
405
406#[derive(Deserialize, Clone, Copy, Default)]
407pub struct IconButton {
408    #[serde(flatten)]
409    pub container: ContainerStyle,
410    pub color: Color,
411    pub icon_width: f32,
412    pub button_width: f32,
413}
414
415#[derive(Deserialize, Default)]
416pub struct ChatMessage {
417    #[serde(flatten)]
418    pub container: ContainerStyle,
419    pub body: TextStyle,
420    pub sender: ContainedText,
421    pub timestamp: ContainedText,
422}
423
424#[derive(Deserialize, Default)]
425pub struct ChannelSelect {
426    #[serde(flatten)]
427    pub container: ContainerStyle,
428    pub header: ChannelName,
429    pub item: ChannelName,
430    pub active_item: ChannelName,
431    pub hovered_item: ChannelName,
432    pub hovered_active_item: ChannelName,
433    pub menu: ContainerStyle,
434}
435
436#[derive(Deserialize, Default)]
437pub struct ChannelName {
438    #[serde(flatten)]
439    pub container: ContainerStyle,
440    pub hash: ContainedText,
441    pub name: TextStyle,
442}
443
444#[derive(Clone, Deserialize, Default)]
445pub struct Picker {
446    #[serde(flatten)]
447    pub container: ContainerStyle,
448    pub empty_container: ContainerStyle,
449    pub input_editor: FieldEditor,
450    pub empty_input_editor: FieldEditor,
451    pub no_matches: ContainedLabel,
452    pub item: Interactive<ContainedLabel>,
453}
454
455#[derive(Clone, Debug, Deserialize, Default)]
456pub struct ContainedText {
457    #[serde(flatten)]
458    pub container: ContainerStyle,
459    #[serde(flatten)]
460    pub text: TextStyle,
461}
462
463#[derive(Clone, Debug, Deserialize, Default)]
464pub struct ContainedLabel {
465    #[serde(flatten)]
466    pub container: ContainerStyle,
467    #[serde(flatten)]
468    pub label: LabelStyle,
469}
470
471#[derive(Clone, Deserialize, Default)]
472pub struct ProjectDiagnostics {
473    #[serde(flatten)]
474    pub container: ContainerStyle,
475    pub empty_message: TextStyle,
476    pub tab_icon_width: f32,
477    pub tab_icon_spacing: f32,
478    pub tab_summary_spacing: f32,
479}
480
481#[derive(Deserialize, Default)]
482pub struct ContactNotification {
483    pub header_avatar: ImageStyle,
484    pub header_message: ContainedText,
485    pub header_height: f32,
486    pub body_message: ContainedText,
487    pub button: Interactive<ContainedText>,
488    pub dismiss_button: Interactive<IconButton>,
489}
490
491#[derive(Deserialize, Default)]
492pub struct UpdateNotification {
493    pub message: ContainedText,
494    pub action_message: Interactive<ContainedText>,
495    pub dismiss_button: Interactive<IconButton>,
496}
497
498#[derive(Deserialize, Default)]
499pub struct MessageNotification {
500    pub message: ContainedText,
501    pub action_message: Interactive<ContainedText>,
502    pub dismiss_button: Interactive<IconButton>,
503}
504
505#[derive(Deserialize, Default)]
506pub struct ProjectSharedNotification {
507    pub window_height: f32,
508    pub window_width: f32,
509    #[serde(default)]
510    pub background: Color,
511    pub owner_container: ContainerStyle,
512    pub owner_avatar: ImageStyle,
513    pub owner_metadata: ContainerStyle,
514    pub owner_username: ContainedText,
515    pub message: ContainedText,
516    pub worktree_roots: ContainedText,
517    pub button_width: f32,
518    pub open_button: ContainedText,
519    pub dismiss_button: ContainedText,
520}
521
522#[derive(Deserialize, Default)]
523pub struct IncomingCallNotification {
524    pub window_height: f32,
525    pub window_width: f32,
526    #[serde(default)]
527    pub background: Color,
528    pub caller_container: ContainerStyle,
529    pub caller_avatar: ImageStyle,
530    pub caller_metadata: ContainerStyle,
531    pub caller_username: ContainedText,
532    pub caller_message: ContainedText,
533    pub worktree_roots: ContainedText,
534    pub button_width: f32,
535    pub accept_button: ContainedText,
536    pub decline_button: ContainedText,
537}
538
539#[derive(Clone, Deserialize, Default)]
540pub struct Editor {
541    pub text_color: Color,
542    #[serde(default)]
543    pub background: Color,
544    pub selection: SelectionStyle,
545    pub gutter_background: Color,
546    pub gutter_padding_factor: f32,
547    pub active_line_background: Color,
548    pub highlighted_line_background: Color,
549    pub rename_fade: f32,
550    pub document_highlight_read_background: Color,
551    pub document_highlight_write_background: Color,
552    pub diff: DiffStyle,
553    pub line_number: Color,
554    pub line_number_active: Color,
555    pub guest_selections: Vec<SelectionStyle>,
556    pub syntax: Arc<SyntaxTheme>,
557    pub diagnostic_path_header: DiagnosticPathHeader,
558    pub diagnostic_header: DiagnosticHeader,
559    pub error_diagnostic: DiagnosticStyle,
560    pub invalid_error_diagnostic: DiagnosticStyle,
561    pub warning_diagnostic: DiagnosticStyle,
562    pub invalid_warning_diagnostic: DiagnosticStyle,
563    pub information_diagnostic: DiagnosticStyle,
564    pub invalid_information_diagnostic: DiagnosticStyle,
565    pub hint_diagnostic: DiagnosticStyle,
566    pub invalid_hint_diagnostic: DiagnosticStyle,
567    pub autocomplete: AutocompleteStyle,
568    pub code_actions: CodeActions,
569    pub unnecessary_code_fade: f32,
570    pub hover_popover: HoverPopover,
571    pub link_definition: HighlightStyle,
572    pub composition_mark: HighlightStyle,
573    pub jump_icon: Interactive<IconButton>,
574    pub scrollbar: Scrollbar,
575}
576
577#[derive(Clone, Deserialize, Default)]
578pub struct Scrollbar {
579    pub track: ContainerStyle,
580    pub thumb: ContainerStyle,
581    pub width: f32,
582    pub min_height_factor: f32,
583}
584
585#[derive(Clone, Deserialize, Default)]
586pub struct DiagnosticPathHeader {
587    #[serde(flatten)]
588    pub container: ContainerStyle,
589    pub filename: ContainedText,
590    pub path: ContainedText,
591    pub text_scale_factor: f32,
592}
593
594#[derive(Clone, Deserialize, Default)]
595pub struct DiagnosticHeader {
596    #[serde(flatten)]
597    pub container: ContainerStyle,
598    pub message: ContainedLabel,
599    pub code: ContainedText,
600    pub text_scale_factor: f32,
601    pub icon_width_factor: f32,
602}
603
604#[derive(Clone, Deserialize, Default)]
605pub struct DiagnosticStyle {
606    pub message: LabelStyle,
607    #[serde(default)]
608    pub header: ContainerStyle,
609    pub text_scale_factor: f32,
610}
611
612#[derive(Clone, Deserialize, Default)]
613pub struct AutocompleteStyle {
614    #[serde(flatten)]
615    pub container: ContainerStyle,
616    pub item: ContainerStyle,
617    pub selected_item: ContainerStyle,
618    pub hovered_item: ContainerStyle,
619    pub match_highlight: HighlightStyle,
620}
621
622#[derive(Clone, Copy, Default, Deserialize)]
623pub struct SelectionStyle {
624    pub cursor: Color,
625    pub selection: Color,
626}
627
628#[derive(Clone, Deserialize, Default)]
629pub struct FieldEditor {
630    #[serde(flatten)]
631    pub container: ContainerStyle,
632    pub text: TextStyle,
633    #[serde(default)]
634    pub placeholder_text: Option<TextStyle>,
635    pub selection: SelectionStyle,
636}
637
638#[derive(Clone, Deserialize, Default)]
639pub struct CodeActions {
640    #[serde(default)]
641    pub indicator: Color,
642    pub vertical_scale: f32,
643}
644
645#[derive(Clone, Deserialize, Default)]
646pub struct DiffStyle {
647    pub inserted: Color,
648    pub modified: Color,
649    pub deleted: Color,
650    pub removed_width_em: f32,
651    pub width_em: f32,
652    pub corner_radius: f32,
653}
654
655#[derive(Debug, Default, Clone, Copy)]
656pub struct Interactive<T> {
657    pub default: T,
658    pub hover: Option<T>,
659    pub clicked: Option<T>,
660    pub active: Option<T>,
661    pub disabled: Option<T>,
662}
663
664impl<T> Interactive<T> {
665    pub fn style_for(&self, state: &mut MouseState, active: bool) -> &T {
666        if active {
667            self.active.as_ref().unwrap_or(&self.default)
668        } else if state.clicked() == Some(gpui::MouseButton::Left) && self.clicked.is_some() {
669            self.clicked.as_ref().unwrap()
670        } else if state.hovered() {
671            self.hover.as_ref().unwrap_or(&self.default)
672        } else {
673            &self.default
674        }
675    }
676
677    pub fn disabled_style(&self) -> &T {
678        self.disabled.as_ref().unwrap_or(&self.default)
679    }
680}
681
682impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
683    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
684    where
685        D: serde::Deserializer<'de>,
686    {
687        #[derive(Deserialize)]
688        struct Helper {
689            #[serde(flatten)]
690            default: Value,
691            hover: Option<Value>,
692            clicked: Option<Value>,
693            active: Option<Value>,
694            disabled: Option<Value>,
695        }
696
697        let json = Helper::deserialize(deserializer)?;
698
699        let deserialize_state = |state_json: Option<Value>| -> Result<Option<T>, D::Error> {
700            if let Some(mut state_json) = state_json {
701                if let Value::Object(state_json) = &mut state_json {
702                    if let Value::Object(default) = &json.default {
703                        for (key, value) in default {
704                            if !state_json.contains_key(key) {
705                                state_json.insert(key.clone(), value.clone());
706                            }
707                        }
708                    }
709                }
710                Ok(Some(
711                    serde_json::from_value::<T>(state_json).map_err(serde::de::Error::custom)?,
712                ))
713            } else {
714                Ok(None)
715            }
716        };
717
718        let hover = deserialize_state(json.hover)?;
719        let clicked = deserialize_state(json.clicked)?;
720        let active = deserialize_state(json.active)?;
721        let disabled = deserialize_state(json.disabled)?;
722        let default = serde_json::from_value(json.default).map_err(serde::de::Error::custom)?;
723
724        Ok(Interactive {
725            default,
726            hover,
727            clicked,
728            active,
729            disabled,
730        })
731    }
732}
733
734impl Editor {
735    pub fn replica_selection_style(&self, replica_id: u16) -> &SelectionStyle {
736        let style_ix = replica_id as usize % (self.guest_selections.len() + 1);
737        if style_ix == 0 {
738            &self.selection
739        } else {
740            &self.guest_selections[style_ix - 1]
741        }
742    }
743}
744
745#[derive(Default)]
746pub struct SyntaxTheme {
747    pub highlights: Vec<(String, HighlightStyle)>,
748}
749
750impl SyntaxTheme {
751    pub fn new(highlights: Vec<(String, HighlightStyle)>) -> Self {
752        Self { highlights }
753    }
754}
755
756impl<'de> Deserialize<'de> for SyntaxTheme {
757    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
758    where
759        D: serde::Deserializer<'de>,
760    {
761        let syntax_data: HashMap<String, HighlightStyle> = Deserialize::deserialize(deserializer)?;
762
763        let mut result = Self::new(Vec::new());
764        for (key, style) in syntax_data {
765            match result
766                .highlights
767                .binary_search_by(|(needle, _)| needle.cmp(&key))
768            {
769                Ok(i) | Err(i) => {
770                    result.highlights.insert(i, (key, style));
771                }
772            }
773        }
774
775        Ok(result)
776    }
777}
778
779#[derive(Clone, Deserialize, Default)]
780pub struct HoverPopover {
781    pub container: ContainerStyle,
782    pub info_container: ContainerStyle,
783    pub warning_container: ContainerStyle,
784    pub error_container: ContainerStyle,
785    pub block_style: ContainerStyle,
786    pub prose: TextStyle,
787    pub highlight: Color,
788}
789
790#[derive(Clone, Deserialize, Default)]
791pub struct TerminalStyle {
792    pub black: Color,
793    pub red: Color,
794    pub green: Color,
795    pub yellow: Color,
796    pub blue: Color,
797    pub magenta: Color,
798    pub cyan: Color,
799    pub white: Color,
800    pub bright_black: Color,
801    pub bright_red: Color,
802    pub bright_green: Color,
803    pub bright_yellow: Color,
804    pub bright_blue: Color,
805    pub bright_magenta: Color,
806    pub bright_cyan: Color,
807    pub bright_white: Color,
808    pub foreground: Color,
809    pub background: Color,
810    pub modal_background: Color,
811    pub cursor: Color,
812    pub dim_black: Color,
813    pub dim_red: Color,
814    pub dim_green: Color,
815    pub dim_yellow: Color,
816    pub dim_blue: Color,
817    pub dim_magenta: Color,
818    pub dim_cyan: Color,
819    pub dim_white: Color,
820    pub bright_foreground: Color,
821    pub dim_foreground: Color,
822}
823
824#[derive(Clone, Deserialize, Default)]
825pub struct ColorScheme {
826    pub name: String,
827    pub is_light: bool,
828
829    pub ramps: RampSet,
830
831    pub lowest: Layer,
832    pub middle: Layer,
833    pub highest: Layer,
834
835    pub popover_shadow: Shadow,
836    pub modal_shadow: Shadow,
837
838    pub players: Vec<Player>,
839}
840
841#[derive(Clone, Deserialize, Default)]
842pub struct Player {
843    pub cursor: Color,
844    pub selection: Color,
845}
846
847#[derive(Clone, Deserialize, Default)]
848pub struct RampSet {
849    pub neutral: Vec<Color>,
850    pub red: Vec<Color>,
851    pub orange: Vec<Color>,
852    pub yellow: Vec<Color>,
853    pub green: Vec<Color>,
854    pub cyan: Vec<Color>,
855    pub blue: Vec<Color>,
856    pub violet: Vec<Color>,
857    pub magenta: Vec<Color>,
858}
859
860#[derive(Clone, Deserialize, Default)]
861pub struct Layer {
862    pub base: StyleSet,
863    pub variant: StyleSet,
864    pub on: StyleSet,
865    pub accent: StyleSet,
866    pub positive: StyleSet,
867    pub warning: StyleSet,
868    pub negative: StyleSet,
869}
870
871#[derive(Clone, Deserialize, Default)]
872pub struct StyleSet {
873    pub default: Style,
874    pub active: Style,
875    pub disabled: Style,
876    pub hovered: Style,
877    pub pressed: Style,
878    pub inverted: Style,
879}
880
881#[derive(Clone, Deserialize, Default)]
882pub struct Style {
883    pub background: Color,
884    pub border: Color,
885    pub foreground: Color,
886}