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