1use std::collections::HashMap;
2
3use gpui::{Hsla, hsla, rgb};
4use twrite_core::{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 link: Hsla,
41 pub custom: HashMap<&'static str, Hsla>,
44 pub error: Hsla,
46 pub warning: Hsla,
48}
49
50impl Default for SyntaxTheme {
51 fn default() -> Self {
52 Self {
53 keyword: rgb(0xcba6f7).into(),
54 function: rgb(0x89b4fa).into(),
55 type_name: rgb(0xf9e2af).into(),
56 string: rgb(0xa6e3a1).into(),
57 number: rgb(0xfab387).into(),
58 comment: rgb(0x6c7086).into(),
59 operator: rgb(0x89dceb).into(),
60 punctuation: rgb(0x9399b2).into(),
61 heading1: rgb(0xffb959).into(),
62 heading2: rgb(0xffb959).into(),
63 heading3: rgb(0xffb959).into(),
64 bold: rgb(0xcdd6f4).into(),
65 italic: rgb(0xb4befe).into(),
66 code: rgb(0xf5c2e7).into(),
67 code_bg: hsla(0.65, 0.4, 0.6, 0.15),
68 link: rgb(0x89b4fa).into(),
69 custom: HashMap::new(),
70 error: rgb(0xf38ba8).into(),
71 warning: rgb(0xf9e2af).into(),
72 }
73 }
74}
75
76impl SyntaxTheme {
77 pub fn set_custom_tag_color(&mut self, name: &'static str, color: Hsla) {
79 self.custom.insert(name, color);
80 }
81}
82
83#[derive(Clone, Debug)]
85pub struct EditorTheme {
86 pub background: Hsla,
88 pub foreground: Hsla,
90 pub cursor: Hsla,
92 pub selection: Hsla,
94 pub search_match: Hsla,
96 pub line_number: Hsla,
98 pub line_number_active: Hsla,
100 pub menu_bg: Hsla,
102 pub menu_border: Hsla,
104 pub menu_hover: Hsla,
106 pub menu_fg: Hsla,
108 pub menu_hint: Hsla,
110 pub syntax: SyntaxTheme,
112}
113
114impl Default for EditorTheme {
115 fn default() -> Self {
116 Self {
117 background: rgb(0x181825).into(),
118 foreground: rgb(0xcdd6f4).into(),
119 cursor: rgb(0xf5e0dc).into(),
120 selection: hsla(0.65, 0.4, 0.6, 0.25),
121 search_match: hsla(0.12, 0.8, 0.65, 0.25),
122 line_number: rgb(0x6c7086).into(),
123 line_number_active: rgb(0xcdd6f4).into(),
124 menu_bg: rgb(0x1e1e2e).into(),
125 menu_border: rgb(0x45475a).into(),
126 menu_hover: rgb(0x313244).into(),
127 menu_fg: rgb(0xcdd6f4).into(),
128 menu_hint: rgb(0x6c7086).into(),
129 syntax: SyntaxTheme::default(),
130 }
131 }
132}
133
134#[derive(Clone, Debug, PartialEq)]
136pub struct ResolvedTokenStyle {
137 pub color: Hsla,
139 pub background: Option<Hsla>,
141 pub bold: bool,
143 pub italic: bool,
145 pub underline: Option<UnderlineDecoration>,
147 pub strikethrough: bool,
149}
150
151impl EditorTheme {
152 pub fn rgba_to_hsla(rgba: Rgba) -> Hsla {
154 let r = rgba.r as f32 / 255.0;
155 let g = rgba.g as f32 / 255.0;
156 let b = rgba.b as f32 / 255.0;
157 let a = rgba.a as f32 / 255.0;
158 gpui::Rgba { r, g, b, a }.into()
159 }
160
161 pub fn tag_color(&self, tag: HighlightTag) -> Hsla {
163 match tag {
164 HighlightTag::Keyword => self.syntax.keyword,
165 HighlightTag::Function => self.syntax.function,
166 HighlightTag::Type => self.syntax.type_name,
167 HighlightTag::String => self.syntax.string,
168 HighlightTag::Number => self.syntax.number,
169 HighlightTag::Comment => self.syntax.comment,
170 HighlightTag::Operator => self.syntax.operator,
171 HighlightTag::Punctuation => self.syntax.punctuation,
172 HighlightTag::Heading(1) => self.syntax.heading1,
173 HighlightTag::Heading(2) => self.syntax.heading2,
174 HighlightTag::Heading(_) => self.syntax.heading3,
175 HighlightTag::Bold => self.syntax.bold,
176 HighlightTag::Italic => self.syntax.italic,
177 HighlightTag::Code => self.syntax.code,
178 HighlightTag::Link => self.syntax.link,
179 HighlightTag::Custom(name) => self
180 .syntax
181 .custom
182 .get(name)
183 .copied()
184 .unwrap_or(self.foreground),
185 HighlightTag::Dimmed => {
186 let mut c = self.syntax.comment;
187 c.a = 0.25;
188 c
189 }
190 HighlightTag::Hidden => {
191 let mut c = self.syntax.comment;
192 c.a = 0.0;
193 c
194 }
195 HighlightTag::Blockquote => self.syntax.comment,
196 HighlightTag::HorizontalRule => self.syntax.punctuation,
197 HighlightTag::TaskUnchecked => self.syntax.comment,
198 HighlightTag::TaskChecked => self.syntax.string,
199 }
200 }
201
202 pub fn resolve_style(&self, style_value: &StyleValue) -> ResolvedTokenStyle {
204 match style_value {
205 StyleValue::Tag(tag) => {
206 let color = self.tag_color(*tag);
207 let bold = matches!(tag, HighlightTag::Heading(_) | HighlightTag::Bold);
208 let italic = matches!(tag, HighlightTag::Italic | HighlightTag::Comment);
209 let background = if matches!(tag, HighlightTag::Code) {
210 Some(self.syntax.code_bg)
211 } else {
212 None
213 };
214 let underline = if matches!(tag, HighlightTag::Link) {
215 Some(UnderlineDecoration::Solid)
216 } else {
217 None
218 };
219
220 ResolvedTokenStyle {
221 color,
222 background,
223 bold,
224 italic,
225 underline,
226 strikethrough: false,
227 }
228 }
229 StyleValue::Direct(direct) => ResolvedTokenStyle {
230 color: direct
231 .color
232 .map(Self::rgba_to_hsla)
233 .unwrap_or(self.foreground),
234 background: direct.background.map(Self::rgba_to_hsla),
235 bold: direct.bold,
236 italic: direct.italic,
237 underline: direct.underline,
238 strikethrough: direct.strikethrough,
239 },
240 }
241 }
242}