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