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