Skip to main content

theme/theme/
syntax.rs

1use gpui::Hsla;
2
3use crate::color;
4
5/// Paint-only syntax colors. The hues follow the Git history graph's lane
6/// palette (indigo, pink, emerald, amber, red, neutral), while light-mode
7/// variants are darkened enough to remain readable as text on white.
8#[derive(Debug, Clone)]
9pub struct SyntaxPalette {
10    pub comment: Hsla,
11    pub keyword: Hsla,
12    pub string: Hsla,
13    pub string_special: Hsla,
14    pub escape: Hsla,
15    pub number: Hsla,
16    pub boolean: Hsla,
17    pub type_name: Hsla,
18    pub type_builtin: Hsla,
19    pub constructor: Hsla,
20    pub function: Hsla,
21    pub function_builtin: Hsla,
22    pub macro_name: Hsla,
23    pub property: Hsla,
24    pub constant: Hsla,
25    pub variable: Hsla,
26    pub variable_special: Hsla,
27    pub parameter: Hsla,
28    pub operator: Hsla,
29    pub punctuation: Hsla,
30    pub tag: Hsla,
31    pub attribute: Hsla,
32    pub label: Hsla,
33    pub invalid: Hsla,
34}
35
36/// The token category a highlighter paints with, mirroring [`SyntaxPalette`]
37/// field-for-field so classification can name a kind without knowing its color.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum HighlightKind {
40    Comment,
41    Keyword,
42    String,
43    StringSpecial,
44    Escape,
45    Number,
46    Boolean,
47    TypeName,
48    TypeBuiltin,
49    Constructor,
50    Function,
51    FunctionBuiltin,
52    MacroName,
53    Property,
54    Constant,
55    Variable,
56    VariableSpecial,
57    Parameter,
58    Operator,
59    Punctuation,
60    Tag,
61    Attribute,
62    Label,
63    Invalid,
64}
65
66impl HighlightKind {
67    pub const ALL: [Self; 24] = [
68        Self::Comment,
69        Self::Keyword,
70        Self::String,
71        Self::StringSpecial,
72        Self::Escape,
73        Self::Number,
74        Self::Boolean,
75        Self::TypeName,
76        Self::TypeBuiltin,
77        Self::Constructor,
78        Self::Function,
79        Self::FunctionBuiltin,
80        Self::MacroName,
81        Self::Property,
82        Self::Constant,
83        Self::Variable,
84        Self::VariableSpecial,
85        Self::Parameter,
86        Self::Operator,
87        Self::Punctuation,
88        Self::Tag,
89        Self::Attribute,
90        Self::Label,
91        Self::Invalid,
92    ];
93
94    /// The kind as a kebab-case word: `string-special`. Stable — a stylesheet
95    /// names kinds by it.
96    pub fn name(self) -> &'static str {
97        match self {
98            Self::Comment => "comment",
99            Self::Keyword => "keyword",
100            Self::String => "string",
101            Self::StringSpecial => "string-special",
102            Self::Escape => "escape",
103            Self::Number => "number",
104            Self::Boolean => "boolean",
105            Self::TypeName => "type",
106            Self::TypeBuiltin => "type-builtin",
107            Self::Constructor => "constructor",
108            Self::Function => "function",
109            Self::FunctionBuiltin => "function-builtin",
110            Self::MacroName => "macro",
111            Self::Property => "property",
112            Self::Constant => "constant",
113            Self::Variable => "variable",
114            Self::VariableSpecial => "variable-special",
115            Self::Parameter => "parameter",
116            Self::Operator => "operator",
117            Self::Punctuation => "punctuation",
118            Self::Tag => "tag",
119            Self::Attribute => "attribute",
120            Self::Label => "label",
121            Self::Invalid => "invalid",
122        }
123    }
124}
125
126impl SyntaxPalette {
127    /// The palette color for a token kind.
128    pub fn color(&self, kind: HighlightKind) -> Hsla {
129        match kind {
130            HighlightKind::Comment => self.comment,
131            HighlightKind::Keyword => self.keyword,
132            HighlightKind::String => self.string,
133            HighlightKind::StringSpecial => self.string_special,
134            HighlightKind::Escape => self.escape,
135            HighlightKind::Number => self.number,
136            HighlightKind::Boolean => self.boolean,
137            HighlightKind::TypeName => self.type_name,
138            HighlightKind::TypeBuiltin => self.type_builtin,
139            HighlightKind::Constructor => self.constructor,
140            HighlightKind::Function => self.function,
141            HighlightKind::FunctionBuiltin => self.function_builtin,
142            HighlightKind::MacroName => self.macro_name,
143            HighlightKind::Property => self.property,
144            HighlightKind::Constant => self.constant,
145            HighlightKind::Variable => self.variable,
146            HighlightKind::VariableSpecial => self.variable_special,
147            HighlightKind::Parameter => self.parameter,
148            HighlightKind::Operator => self.operator,
149            HighlightKind::Punctuation => self.punctuation,
150            HighlightKind::Tag => self.tag,
151            HighlightKind::Attribute => self.attribute,
152            HighlightKind::Label => self.label,
153            HighlightKind::Invalid => self.invalid,
154        }
155    }
156
157    pub(crate) fn dark(text: Hsla, comment: Hsla, danger: Hsla) -> Self {
158        // Same sources and 72% saturation treatment as history::graph_color.
159        let indigo = git_graph_tone(color::oklch(0.673, 0.182, 276.935));
160        let pink = git_graph_tone(color::oklch(0.718, 0.202, 349.761));
161        let emerald = git_graph_tone(color::oklch(0.765, 0.177, 163.223));
162        let amber = git_graph_tone(color::oklch(0.828, 0.189, 84.429));
163        let red = git_graph_tone(danger);
164        Self {
165            comment,
166            keyword: indigo,
167            string: emerald,
168            string_special: pink,
169            escape: pink,
170            number: amber,
171            boolean: amber,
172            type_name: amber,
173            type_builtin: emerald,
174            constructor: amber,
175            function: indigo,
176            function_builtin: pink,
177            macro_name: pink,
178            property: amber,
179            constant: emerald,
180            variable: text,
181            variable_special: pink,
182            parameter: text,
183            operator: text,
184            punctuation: text,
185            tag: pink,
186            attribute: amber,
187            label: amber,
188            invalid: red,
189        }
190    }
191
192    pub(crate) fn light(text: Hsla, comment: Hsla, danger: Hsla) -> Self {
193        // Match the light graph's hue families at text-safe lightness.
194        let indigo = git_graph_tone(color::oklch(0.47, 0.20, 276.966));
195        let pink = git_graph_tone(color::oklch(0.47, 0.17, 0.584));
196        let emerald = git_graph_tone(color::oklch(0.46, 0.11, 163.225));
197        let amber = git_graph_tone(color::oklch(0.47, 0.12, 48.998));
198        let red = git_graph_tone(danger);
199        Self {
200            comment,
201            keyword: indigo,
202            string: emerald,
203            string_special: pink,
204            escape: pink,
205            number: amber,
206            boolean: amber,
207            type_name: amber,
208            type_builtin: emerald,
209            constructor: amber,
210            function: indigo,
211            function_builtin: pink,
212            macro_name: pink,
213            property: amber,
214            constant: emerald,
215            variable: text,
216            variable_special: pink,
217            parameter: text,
218            operator: text,
219            punctuation: text,
220            tag: pink,
221            attribute: amber,
222            label: amber,
223            invalid: red,
224        }
225    }
226}
227
228/// Git history intentionally softens lane saturation so the graph remains
229/// colorful without competing with content. Syntax uses the same treatment.
230fn git_graph_tone(mut color: Hsla) -> Hsla {
231    color.s *= 0.72;
232    color
233}