1use std::collections::HashMap;
2
3use gpui::{Hsla, hsla, rgb};
4use twrite_core::{CalloutKind, HighlightTag, Rgba, StyleValue, UnderlineDecoration};
5
6#[derive(Clone, Debug)]
8pub struct SyntaxTheme {
9 pub keyword: Hsla,
11 pub function: Hsla,
13 pub type_name: Hsla,
15 pub string: Hsla,
17 pub number: Hsla,
19 pub comment: Hsla,
21 pub operator: Hsla,
23 pub punctuation: Hsla,
25 pub heading1: Hsla,
27 pub heading2: Hsla,
29 pub heading3: Hsla,
31 pub bold: Hsla,
33 pub italic: Hsla,
35 pub code: Hsla,
37 pub code_bg: Hsla,
39 pub highlight_bg: Hsla,
41 pub callout_note: Hsla,
43 pub callout_tip: Hsla,
45 pub callout_warning: Hsla,
47 pub callout_caution: Hsla,
49 pub callout_important: Hsla,
51 pub link: Hsla,
53 pub custom: HashMap<&'static str, Hsla>,
56 pub error: Hsla,
58 pub warning: Hsla,
60}
61
62impl Default for SyntaxTheme {
63 fn default() -> Self {
64 Self {
65 keyword: rgb(0xcba6f7).into(),
66 function: rgb(0x89b4fa).into(),
67 type_name: rgb(0xf9e2af).into(),
68 string: rgb(0xa6e3a1).into(),
69 number: rgb(0xfab387).into(),
70 comment: rgb(0x6c7086).into(),
71 operator: rgb(0x89dceb).into(),
72 punctuation: rgb(0x9399b2).into(),
73 heading1: rgb(0xffb959).into(),
74 heading2: rgb(0xffb959).into(),
75 heading3: rgb(0xffb959).into(),
76 bold: rgb(0xcdd6f4).into(),
77 italic: rgb(0xb4befe).into(),
78 code: rgb(0xf5c2e7).into(),
79 code_bg: hsla(0.65, 0.4, 0.6, 0.15),
80 highlight_bg: hsla(0.11, 0.85, 0.6, 0.35),
81 callout_note: rgb(0x89b4fa).into(),
82 callout_tip: rgb(0xa6e3a1).into(),
83 callout_warning: rgb(0xf9e2af).into(),
84 callout_caution: rgb(0xfab387).into(),
85 callout_important: rgb(0xf38ba8).into(),
86 link: rgb(0x89b4fa).into(),
87 custom: HashMap::new(),
88 error: rgb(0xf38ba8).into(),
89 warning: rgb(0xf9e2af).into(),
90 }
91 }
92}
93
94impl SyntaxTheme {
95 pub fn set_custom_tag_color(&mut self, name: &'static str, color: Hsla) {
97 self.custom.insert(name, color);
98 }
99
100 pub fn callout_accent(&self, kind: CalloutKind) -> Hsla {
103 match kind {
104 CalloutKind::Note => self.callout_note,
105 CalloutKind::Tip => self.callout_tip,
106 CalloutKind::Warning => self.callout_warning,
107 CalloutKind::Caution => self.callout_caution,
108 CalloutKind::Important => self.callout_important,
109 CalloutKind::Other => self.callout_note,
110 }
111 }
112}
113
114#[derive(Clone, Debug)]
116pub struct EditorTheme {
117 pub background: Hsla,
119 pub foreground: Hsla,
121 pub cursor: Hsla,
123 pub selection: Hsla,
125 pub search_match: Hsla,
127 pub line_number: Hsla,
129 pub line_number_active: Hsla,
131 pub menu_bg: Hsla,
133 pub menu_border: Hsla,
135 pub menu_hover: Hsla,
137 pub menu_fg: Hsla,
139 pub menu_hint: Hsla,
141 pub syntax: SyntaxTheme,
143}
144
145impl Default for EditorTheme {
146 fn default() -> Self {
147 Self {
148 background: rgb(0x181825).into(),
149 foreground: rgb(0xcdd6f4).into(),
150 cursor: rgb(0xf5e0dc).into(),
151 selection: hsla(0.65, 0.4, 0.6, 0.25),
152 search_match: hsla(0.12, 0.8, 0.65, 0.25),
153 line_number: rgb(0x6c7086).into(),
154 line_number_active: rgb(0xcdd6f4).into(),
155 menu_bg: rgb(0x1e1e2e).into(),
156 menu_border: rgb(0x45475a).into(),
157 menu_hover: rgb(0x313244).into(),
158 menu_fg: rgb(0xcdd6f4).into(),
159 menu_hint: rgb(0x6c7086).into(),
160 syntax: SyntaxTheme::default(),
161 }
162 }
163}
164
165#[derive(Clone, Debug, PartialEq)]
167pub struct ResolvedTokenStyle {
168 pub color: Hsla,
170 pub background: Option<Hsla>,
172 pub bold: bool,
174 pub italic: bool,
176 pub underline: Option<UnderlineDecoration>,
178 pub strikethrough: bool,
180}
181
182impl EditorTheme {
183 pub fn rgba_to_hsla(rgba: Rgba) -> Hsla {
185 let r = rgba.r as f32 / 255.0;
186 let g = rgba.g as f32 / 255.0;
187 let b = rgba.b as f32 / 255.0;
188 let a = rgba.a as f32 / 255.0;
189 gpui::Rgba { r, g, b, a }.into()
190 }
191
192 pub fn tag_color(&self, tag: HighlightTag) -> Hsla {
194 match tag {
195 HighlightTag::Keyword => self.syntax.keyword,
196 HighlightTag::Function => self.syntax.function,
197 HighlightTag::Type => self.syntax.type_name,
198 HighlightTag::String => self.syntax.string,
199 HighlightTag::Number => self.syntax.number,
200 HighlightTag::Comment => self.syntax.comment,
201 HighlightTag::Operator => self.syntax.operator,
202 HighlightTag::Punctuation => self.syntax.punctuation,
203 HighlightTag::Heading(1) => self.syntax.heading1,
204 HighlightTag::Heading(2) => self.syntax.heading2,
205 HighlightTag::Heading(_) => self.syntax.heading3,
206 HighlightTag::Bold => self.syntax.bold,
207 HighlightTag::Italic => self.syntax.italic,
208 HighlightTag::Code => self.syntax.code,
209 HighlightTag::Highlight => self.foreground,
210 HighlightTag::Link => self.syntax.link,
211 HighlightTag::Custom(name) => self
212 .syntax
213 .custom
214 .get(name)
215 .copied()
216 .unwrap_or(self.foreground),
217 HighlightTag::Dimmed => {
218 let mut c = self.syntax.comment;
219 c.a = 0.25;
220 c
221 }
222 HighlightTag::Hidden => {
223 let mut c = self.syntax.comment;
224 c.a = 0.0;
225 c
226 }
227 HighlightTag::Blockquote => self.syntax.comment,
228 HighlightTag::Callout(_) => self.foreground,
229 HighlightTag::HorizontalRule => self.syntax.punctuation,
230 HighlightTag::TaskUnchecked => self.syntax.comment,
231 HighlightTag::TaskChecked => self.syntax.string,
232 }
233 }
234
235 pub fn resolve_style(&self, style_value: &StyleValue) -> ResolvedTokenStyle {
237 match style_value {
238 StyleValue::Tag(tag) => {
239 let color = self.tag_color(*tag);
240 let bold = matches!(tag, HighlightTag::Heading(_) | HighlightTag::Bold);
241 let italic = matches!(tag, HighlightTag::Italic | HighlightTag::Comment);
242 let background = if matches!(tag, HighlightTag::Code) {
243 Some(self.syntax.code_bg)
244 } else if matches!(tag, HighlightTag::Highlight) {
245 Some(self.syntax.highlight_bg)
246 } else {
247 None
248 };
249 let underline = if matches!(tag, HighlightTag::Link) {
250 Some(UnderlineDecoration::Solid)
251 } else {
252 None
253 };
254
255 ResolvedTokenStyle {
256 color,
257 background,
258 bold,
259 italic,
260 underline,
261 strikethrough: false,
262 }
263 }
264 StyleValue::Direct(direct) => ResolvedTokenStyle {
265 color: direct
266 .color
267 .map(Self::rgba_to_hsla)
268 .unwrap_or(self.foreground),
269 background: direct.background.map(Self::rgba_to_hsla),
270 bold: direct.bold,
271 italic: direct.italic,
272 underline: direct.underline,
273 strikethrough: direct.strikethrough,
274 },
275 }
276 }
277}