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