1mod highlight_map;
2mod resolution;
3mod theme_registry;
4
5use crate::editor::{EditorStyle, SelectionStyle};
6use anyhow::Result;
7use gpui::{
8 color::Color,
9 elements::{ContainerStyle, ImageStyle, LabelStyle},
10 fonts::{HighlightStyle, TextStyle},
11 Border,
12};
13use serde::Deserialize;
14use std::collections::HashMap;
15
16pub use highlight_map::*;
17pub use theme_registry::*;
18
19pub const DEFAULT_THEME_NAME: &'static str = "black";
20
21#[derive(Deserialize)]
22pub struct Theme {
23 #[serde(default)]
24 pub name: String,
25 pub workspace: Workspace,
26 pub chat_panel: ChatPanel,
27 pub people_panel: PeoplePanel,
28 pub selector: Selector,
29 pub editor: EditorStyle,
30 pub syntax: SyntaxTheme,
31}
32
33pub struct SyntaxTheme {
34 highlights: Vec<(String, HighlightStyle)>,
35}
36
37#[derive(Deserialize)]
38pub struct Workspace {
39 pub background: Color,
40 pub titlebar: Titlebar,
41 pub tab: Tab,
42 pub active_tab: Tab,
43 pub pane_divider: Border,
44 pub left_sidebar: Sidebar,
45 pub right_sidebar: Sidebar,
46}
47
48#[derive(Clone, Deserialize)]
49pub struct Titlebar {
50 #[serde(flatten)]
51 pub container: ContainerStyle,
52 pub title: TextStyle,
53 pub avatar_width: f32,
54 pub offline_icon: OfflineIcon,
55 pub icon_color: Color,
56 pub avatar: ImageStyle,
57}
58
59#[derive(Clone, Deserialize)]
60pub struct OfflineIcon {
61 #[serde(flatten)]
62 pub container: ContainerStyle,
63 pub width: f32,
64}
65
66#[derive(Clone, Deserialize)]
67pub struct Tab {
68 #[serde(flatten)]
69 pub container: ContainerStyle,
70 #[serde(flatten)]
71 pub label: LabelStyle,
72 pub spacing: f32,
73 pub icon_width: f32,
74 pub icon_close: Color,
75 pub icon_close_active: Color,
76 pub icon_dirty: Color,
77 pub icon_conflict: Color,
78}
79
80#[derive(Deserialize)]
81pub struct Sidebar {
82 #[serde(flatten)]
83 pub container: ContainerStyle,
84 pub width: f32,
85 pub icon: SidebarIcon,
86 pub active_icon: SidebarIcon,
87 pub resize_handle: ContainerStyle,
88}
89
90#[derive(Deserialize)]
91pub struct SidebarIcon {
92 pub color: Color,
93 pub height: f32,
94}
95
96#[derive(Deserialize)]
97pub struct ChatPanel {
98 #[serde(flatten)]
99 pub container: ContainerStyle,
100 pub message: ChatMessage,
101 pub pending_message: ChatMessage,
102 pub channel_select: ChannelSelect,
103 pub input_editor: InputEditorStyle,
104 pub sign_in_prompt: TextStyle,
105 pub hovered_sign_in_prompt: TextStyle,
106}
107
108#[derive(Deserialize)]
109pub struct PeoplePanel {
110 #[serde(flatten)]
111 pub container: ContainerStyle,
112 pub host_username: TextStyle,
113 pub worktree_name: ContainedText,
114 pub worktree_host_avatar: ImageStyle,
115 pub worktree_guest_avatar: ImageStyle,
116}
117
118#[derive(Deserialize)]
119pub struct ChatMessage {
120 #[serde(flatten)]
121 pub container: ContainerStyle,
122 pub body: TextStyle,
123 pub sender: ContainedText,
124 pub timestamp: ContainedText,
125}
126
127#[derive(Deserialize)]
128pub struct ChannelSelect {
129 #[serde(flatten)]
130 pub container: ContainerStyle,
131 pub header: ChannelName,
132 pub item: ChannelName,
133 pub active_item: ChannelName,
134 pub hovered_item: ChannelName,
135 pub hovered_active_item: ChannelName,
136 pub menu: ContainerStyle,
137}
138
139#[derive(Deserialize)]
140pub struct ChannelName {
141 #[serde(flatten)]
142 pub container: ContainerStyle,
143 pub hash: ContainedText,
144 pub name: TextStyle,
145}
146
147#[derive(Deserialize)]
148pub struct Selector {
149 #[serde(flatten)]
150 pub container: ContainerStyle,
151 pub empty: ContainedLabel,
152 pub input_editor: InputEditorStyle,
153 pub item: ContainedLabel,
154 pub active_item: ContainedLabel,
155}
156
157#[derive(Deserialize)]
158pub struct ContainedText {
159 #[serde(flatten)]
160 pub container: ContainerStyle,
161 #[serde(flatten)]
162 pub text: TextStyle,
163}
164
165#[derive(Deserialize)]
166pub struct ContainedLabel {
167 #[serde(flatten)]
168 pub container: ContainerStyle,
169 #[serde(flatten)]
170 pub label: LabelStyle,
171}
172
173#[derive(Clone, Deserialize)]
174pub struct InputEditorStyle {
175 #[serde(flatten)]
176 pub container: ContainerStyle,
177 pub text: TextStyle,
178 #[serde(default)]
179 pub placeholder_text: Option<TextStyle>,
180 pub selection: SelectionStyle,
181}
182
183impl SyntaxTheme {
184 pub fn new(highlights: Vec<(String, HighlightStyle)>) -> Self {
185 Self { highlights }
186 }
187
188 pub fn highlight_style(&self, id: HighlightId) -> Option<HighlightStyle> {
189 self.highlights
190 .get(id.0 as usize)
191 .map(|entry| entry.1.clone())
192 }
193
194 #[cfg(test)]
195 pub fn highlight_name(&self, id: HighlightId) -> Option<&str> {
196 self.highlights.get(id.0 as usize).map(|e| e.0.as_str())
197 }
198}
199
200impl InputEditorStyle {
201 pub fn as_editor(&self) -> EditorStyle {
202 EditorStyle {
203 text: self.text.clone(),
204 placeholder_text: self.placeholder_text.clone(),
205 background: self
206 .container
207 .background_color
208 .unwrap_or(Color::transparent_black()),
209 selection: self.selection,
210 gutter_background: Default::default(),
211 active_line_background: Default::default(),
212 line_number: Default::default(),
213 line_number_active: Default::default(),
214 guest_selections: Default::default(),
215 }
216 }
217}
218
219impl<'de> Deserialize<'de> for SyntaxTheme {
220 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
221 where
222 D: serde::Deserializer<'de>,
223 {
224 let syntax_data: HashMap<String, HighlightStyle> = Deserialize::deserialize(deserializer)?;
225
226 let mut result = Self::new(Vec::new());
227 for (key, style) in syntax_data {
228 match result
229 .highlights
230 .binary_search_by(|(needle, _)| needle.cmp(&key))
231 {
232 Ok(i) | Err(i) => {
233 result.highlights.insert(i, (key, style));
234 }
235 }
236 }
237
238 Ok(result)
239 }
240}