1mod theme_registry;
2
3use gpui::{
4 color::Color,
5 elements::{ContainerStyle, ImageStyle, LabelStyle},
6 fonts::{HighlightStyle, TextStyle},
7 Border,
8};
9use serde::{de::DeserializeOwned, Deserialize};
10use serde_json::Value;
11use std::{collections::HashMap, sync::Arc};
12
13pub use theme_registry::*;
14
15pub const DEFAULT_THEME_NAME: &'static str = "dark";
16
17#[derive(Deserialize, Default)]
18pub struct Theme {
19 #[serde(default)]
20 pub name: String,
21 pub workspace: Workspace,
22 pub chat_panel: ChatPanel,
23 pub contacts_panel: ContactsPanel,
24 pub project_panel: ProjectPanel,
25 pub command_palette: CommandPalette,
26 pub selector: Selector,
27 pub editor: Editor,
28 pub search: Search,
29 pub project_diagnostics: ProjectDiagnostics,
30 pub breadcrumbs: ContainedText,
31}
32
33#[derive(Deserialize, Default)]
34pub struct Workspace {
35 pub background: Color,
36 pub titlebar: Titlebar,
37 pub tab: Tab,
38 pub active_tab: Tab,
39 pub pane_divider: Border,
40 pub leader_border_opacity: f32,
41 pub leader_border_width: f32,
42 pub sidebar_resize_handle: ContainerStyle,
43 pub status_bar: StatusBar,
44 pub toolbar: Toolbar,
45 pub disconnected_overlay: ContainedText,
46 pub modal: ContainerStyle,
47}
48
49#[derive(Clone, Deserialize, Default)]
50pub struct Titlebar {
51 #[serde(flatten)]
52 pub container: ContainerStyle,
53 pub height: f32,
54 pub title: TextStyle,
55 pub avatar_width: f32,
56 pub avatar_ribbon: AvatarRibbon,
57 pub offline_icon: OfflineIcon,
58 pub share_icon: Interactive<ShareIcon>,
59 pub avatar: ImageStyle,
60 pub sign_in_prompt: Interactive<ContainedText>,
61 pub outdated_warning: ContainedText,
62}
63
64#[derive(Clone, Deserialize, Default)]
65pub struct AvatarRibbon {
66 #[serde(flatten)]
67 pub container: ContainerStyle,
68 pub width: f32,
69 pub height: f32,
70}
71
72#[derive(Clone, Deserialize, Default)]
73pub struct OfflineIcon {
74 #[serde(flatten)]
75 pub container: ContainerStyle,
76 pub width: f32,
77 pub color: Color,
78}
79
80#[derive(Clone, Deserialize, Default)]
81pub struct ShareIcon {
82 #[serde(flatten)]
83 pub container: ContainerStyle,
84 pub color: Color,
85}
86
87#[derive(Clone, Deserialize, Default)]
88pub struct Tab {
89 pub height: f32,
90 #[serde(flatten)]
91 pub container: ContainerStyle,
92 #[serde(flatten)]
93 pub label: LabelStyle,
94 pub spacing: f32,
95 pub icon_width: f32,
96 pub icon_close: Color,
97 pub icon_close_active: Color,
98 pub icon_dirty: Color,
99 pub icon_conflict: Color,
100}
101
102#[derive(Clone, Deserialize, Default)]
103pub struct Toolbar {
104 #[serde(flatten)]
105 pub container: ContainerStyle,
106 pub height: f32,
107 pub item_spacing: f32,
108}
109
110#[derive(Clone, Deserialize, Default)]
111pub struct Search {
112 #[serde(flatten)]
113 pub container: ContainerStyle,
114 pub editor: FindEditor,
115 pub invalid_editor: ContainerStyle,
116 pub option_button_group: ContainerStyle,
117 pub option_button: ContainedText,
118 pub active_option_button: ContainedText,
119 pub hovered_option_button: ContainedText,
120 pub active_hovered_option_button: ContainedText,
121 pub match_background: Color,
122 pub match_index: ContainedText,
123 pub results_status: TextStyle,
124 pub tab_icon_width: f32,
125 pub tab_icon_spacing: f32,
126}
127
128#[derive(Clone, Deserialize, Default)]
129pub struct FindEditor {
130 #[serde(flatten)]
131 pub input: FieldEditor,
132 pub min_width: f32,
133 pub max_width: f32,
134}
135
136#[derive(Deserialize, Default)]
137pub struct StatusBar {
138 #[serde(flatten)]
139 pub container: ContainerStyle,
140 pub height: f32,
141 pub item_spacing: f32,
142 pub cursor_position: TextStyle,
143 pub auto_update_progress_message: TextStyle,
144 pub auto_update_done_message: TextStyle,
145 pub lsp_status: Interactive<StatusBarLspStatus>,
146 pub sidebar_buttons: StatusBarSidebarButtons,
147 pub diagnostic_summary: Interactive<StatusBarDiagnosticSummary>,
148 pub diagnostic_message: Interactive<ContainedText>,
149}
150
151#[derive(Deserialize, Default)]
152pub struct StatusBarSidebarButtons {
153 pub group_left: ContainerStyle,
154 pub group_right: ContainerStyle,
155 pub item: Interactive<SidebarItem>,
156}
157
158#[derive(Deserialize, Default)]
159pub struct StatusBarDiagnosticSummary {
160 pub container_ok: ContainerStyle,
161 pub container_warning: ContainerStyle,
162 pub container_error: ContainerStyle,
163 pub text: TextStyle,
164 pub icon_color_ok: Color,
165 pub icon_color_warning: Color,
166 pub icon_color_error: Color,
167 pub height: f32,
168 pub icon_width: f32,
169 pub icon_spacing: f32,
170 pub summary_spacing: f32,
171}
172
173#[derive(Deserialize, Default)]
174pub struct StatusBarLspStatus {
175 #[serde(flatten)]
176 pub container: ContainerStyle,
177 pub height: f32,
178 pub icon_spacing: f32,
179 pub icon_color: Color,
180 pub icon_width: f32,
181 pub message: TextStyle,
182}
183
184#[derive(Deserialize, Default)]
185pub struct Sidebar {
186 pub resize_handle: ContainerStyle,
187}
188
189#[derive(Clone, Copy, Deserialize, Default)]
190pub struct SidebarItem {
191 #[serde(flatten)]
192 pub container: ContainerStyle,
193 pub icon_color: Color,
194 pub icon_size: f32,
195}
196
197#[derive(Deserialize, Default)]
198pub struct ChatPanel {
199 #[serde(flatten)]
200 pub container: ContainerStyle,
201 pub message: ChatMessage,
202 pub pending_message: ChatMessage,
203 pub channel_select: ChannelSelect,
204 pub input_editor: FieldEditor,
205 pub sign_in_prompt: TextStyle,
206 pub hovered_sign_in_prompt: TextStyle,
207}
208
209#[derive(Debug, Deserialize, Default)]
210pub struct ProjectPanel {
211 #[serde(flatten)]
212 pub container: ContainerStyle,
213 pub entry: ProjectPanelEntry,
214 pub hovered_entry: ProjectPanelEntry,
215 pub selected_entry: ProjectPanelEntry,
216 pub hovered_selected_entry: ProjectPanelEntry,
217}
218
219#[derive(Debug, Deserialize, Default)]
220pub struct ProjectPanelEntry {
221 pub height: f32,
222 #[serde(flatten)]
223 pub container: ContainerStyle,
224 pub text: TextStyle,
225 pub icon_color: Color,
226 pub icon_size: f32,
227 pub icon_spacing: f32,
228}
229
230#[derive(Debug, Deserialize, Default)]
231pub struct CommandPalette {
232 pub key: ContainedLabel,
233 pub keystroke_spacing: f32,
234}
235
236#[derive(Deserialize, Default)]
237pub struct ContactsPanel {
238 #[serde(flatten)]
239 pub container: ContainerStyle,
240 pub host_row_height: f32,
241 pub host_avatar: ImageStyle,
242 pub host_username: ContainedText,
243 pub tree_branch_width: f32,
244 pub tree_branch_color: Color,
245 pub shared_project: WorktreeRow,
246 pub hovered_shared_project: WorktreeRow,
247 pub unshared_project: WorktreeRow,
248 pub hovered_unshared_project: WorktreeRow,
249}
250
251#[derive(Deserialize, Default)]
252pub struct WorktreeRow {
253 #[serde(flatten)]
254 pub container: ContainerStyle,
255 pub height: f32,
256 pub name: ContainedText,
257 pub guest_avatar: ImageStyle,
258 pub guest_avatar_spacing: f32,
259}
260
261#[derive(Deserialize, Default)]
262pub struct ChatMessage {
263 #[serde(flatten)]
264 pub container: ContainerStyle,
265 pub body: TextStyle,
266 pub sender: ContainedText,
267 pub timestamp: ContainedText,
268}
269
270#[derive(Deserialize, Default)]
271pub struct ChannelSelect {
272 #[serde(flatten)]
273 pub container: ContainerStyle,
274 pub header: ChannelName,
275 pub item: ChannelName,
276 pub active_item: ChannelName,
277 pub hovered_item: ChannelName,
278 pub hovered_active_item: ChannelName,
279 pub menu: ContainerStyle,
280}
281
282#[derive(Deserialize, Default)]
283pub struct ChannelName {
284 #[serde(flatten)]
285 pub container: ContainerStyle,
286 pub hash: ContainedText,
287 pub name: TextStyle,
288}
289
290#[derive(Deserialize, Default)]
291pub struct Selector {
292 #[serde(flatten)]
293 pub container: ContainerStyle,
294 pub empty: ContainedLabel,
295 pub input_editor: FieldEditor,
296 pub item: ContainedLabel,
297 pub active_item: ContainedLabel,
298}
299
300#[derive(Clone, Debug, Deserialize, Default)]
301pub struct ContainedText {
302 #[serde(flatten)]
303 pub container: ContainerStyle,
304 #[serde(flatten)]
305 pub text: TextStyle,
306}
307
308#[derive(Clone, Debug, Deserialize, Default)]
309pub struct ContainedLabel {
310 #[serde(flatten)]
311 pub container: ContainerStyle,
312 #[serde(flatten)]
313 pub label: LabelStyle,
314}
315
316#[derive(Clone, Deserialize, Default)]
317pub struct ProjectDiagnostics {
318 #[serde(flatten)]
319 pub container: ContainerStyle,
320 pub empty_message: TextStyle,
321 pub tab_icon_width: f32,
322 pub tab_icon_spacing: f32,
323 pub tab_summary_spacing: f32,
324}
325
326#[derive(Clone, Deserialize, Default)]
327pub struct Editor {
328 pub text_color: Color,
329 #[serde(default)]
330 pub background: Color,
331 pub selection: SelectionStyle,
332 pub gutter_background: Color,
333 pub gutter_padding_factor: f32,
334 pub active_line_background: Color,
335 pub highlighted_line_background: Color,
336 pub rename_fade: f32,
337 pub document_highlight_read_background: Color,
338 pub document_highlight_write_background: Color,
339 pub diff_background_deleted: Color,
340 pub diff_background_inserted: Color,
341 pub line_number: Color,
342 pub line_number_active: Color,
343 pub guest_selections: Vec<SelectionStyle>,
344 pub syntax: Arc<SyntaxTheme>,
345 pub diagnostic_path_header: DiagnosticPathHeader,
346 pub diagnostic_header: DiagnosticHeader,
347 pub error_diagnostic: DiagnosticStyle,
348 pub invalid_error_diagnostic: DiagnosticStyle,
349 pub warning_diagnostic: DiagnosticStyle,
350 pub invalid_warning_diagnostic: DiagnosticStyle,
351 pub information_diagnostic: DiagnosticStyle,
352 pub invalid_information_diagnostic: DiagnosticStyle,
353 pub hint_diagnostic: DiagnosticStyle,
354 pub invalid_hint_diagnostic: DiagnosticStyle,
355 pub autocomplete: AutocompleteStyle,
356 pub code_actions_indicator: Color,
357 pub unnecessary_code_fade: f32,
358}
359
360#[derive(Clone, Deserialize, Default)]
361pub struct DiagnosticPathHeader {
362 #[serde(flatten)]
363 pub container: ContainerStyle,
364 pub filename: ContainedText,
365 pub path: ContainedText,
366 pub text_scale_factor: f32,
367}
368
369#[derive(Clone, Deserialize, Default)]
370pub struct DiagnosticHeader {
371 #[serde(flatten)]
372 pub container: ContainerStyle,
373 pub message: ContainedLabel,
374 pub code: ContainedText,
375 pub text_scale_factor: f32,
376 pub icon_width_factor: f32,
377}
378
379#[derive(Clone, Deserialize, Default)]
380pub struct DiagnosticStyle {
381 pub message: LabelStyle,
382 #[serde(default)]
383 pub header: ContainerStyle,
384 pub text_scale_factor: f32,
385}
386
387#[derive(Clone, Deserialize, Default)]
388pub struct AutocompleteStyle {
389 #[serde(flatten)]
390 pub container: ContainerStyle,
391 pub item: ContainerStyle,
392 pub selected_item: ContainerStyle,
393 pub hovered_item: ContainerStyle,
394 pub match_highlight: HighlightStyle,
395}
396
397#[derive(Clone, Copy, Default, Deserialize)]
398pub struct SelectionStyle {
399 pub cursor: Color,
400 pub selection: Color,
401}
402
403#[derive(Clone, Deserialize, Default)]
404pub struct FieldEditor {
405 #[serde(flatten)]
406 pub container: ContainerStyle,
407 pub text: TextStyle,
408 #[serde(default)]
409 pub placeholder_text: Option<TextStyle>,
410 pub selection: SelectionStyle,
411}
412
413#[derive(Default, Clone, Copy)]
414pub struct Interactive<T> {
415 pub default: T,
416 pub hover: Option<T>,
417 pub active: Option<T>,
418 pub active_hover: Option<T>,
419}
420
421impl<T> Interactive<T> {
422 pub fn active(&self) -> &T {
423 self.active.as_ref().unwrap_or(&self.default)
424 }
425
426 pub fn hover(&self) -> &T {
427 self.hover.as_ref().unwrap_or(&self.default)
428 }
429
430 pub fn active_hover(&self) -> &T {
431 self.active_hover.as_ref().unwrap_or(self.active())
432 }
433}
434
435impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
436 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
437 where
438 D: serde::Deserializer<'de>,
439 {
440 #[derive(Deserialize)]
441 struct Helper {
442 #[serde(flatten)]
443 default: Value,
444 hover: Option<Value>,
445 active: Option<Value>,
446 active_hover: Option<Value>,
447 }
448
449 let json = Helper::deserialize(deserializer)?;
450
451 let deserialize_state = |state_json: Option<Value>| -> Result<Option<T>, D::Error> {
452 if let Some(mut state_json) = state_json {
453 if let Value::Object(state_json) = &mut state_json {
454 if let Value::Object(default) = &json.default {
455 for (key, value) in default {
456 if !state_json.contains_key(key) {
457 state_json.insert(key.clone(), value.clone());
458 }
459 }
460 }
461 }
462 Ok(Some(
463 serde_json::from_value::<T>(state_json).map_err(serde::de::Error::custom)?,
464 ))
465 } else {
466 Ok(None)
467 }
468 };
469
470 let hover = deserialize_state(json.hover)?;
471 let active = deserialize_state(json.active)?;
472 let active_hover = deserialize_state(json.active_hover)?;
473 let default = serde_json::from_value(json.default).map_err(serde::de::Error::custom)?;
474
475 Ok(Interactive {
476 default,
477 hover,
478 active,
479 active_hover,
480 })
481 }
482}
483
484impl Editor {
485 pub fn replica_selection_style(&self, replica_id: u16) -> &SelectionStyle {
486 let style_ix = replica_id as usize % (self.guest_selections.len() + 1);
487 if style_ix == 0 {
488 &self.selection
489 } else {
490 &self.guest_selections[style_ix - 1]
491 }
492 }
493}
494
495#[derive(Default)]
496pub struct SyntaxTheme {
497 pub highlights: Vec<(String, HighlightStyle)>,
498}
499
500impl SyntaxTheme {
501 pub fn new(highlights: Vec<(String, HighlightStyle)>) -> Self {
502 Self { highlights }
503 }
504}
505
506impl<'de> Deserialize<'de> for SyntaxTheme {
507 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
508 where
509 D: serde::Deserializer<'de>,
510 {
511 let syntax_data: HashMap<String, HighlightStyle> = Deserialize::deserialize(deserializer)?;
512
513 let mut result = Self::new(Vec::new());
514 for (key, style) in syntax_data {
515 match result
516 .highlights
517 .binary_search_by(|(needle, _)| needle.cmp(&key))
518 {
519 Ok(i) | Err(i) => {
520 result.highlights.insert(i, (key, style));
521 }
522 }
523 }
524
525 Ok(result)
526 }
527}