1import deepmerge from "deepmerge"
2import { FontWeight, fontWeights } from "../../common"
3import { ColorScheme } from "./colorScheme"
4
5export interface SyntaxHighlightStyle {
6 color: string
7 weight?: FontWeight
8 underline?: boolean
9 italic?: boolean
10}
11
12export interface Syntax {
13 // == Text Styles ====== /
14 comment: SyntaxHighlightStyle
15 // elixir: doc comment
16 "comment.doc": SyntaxHighlightStyle
17 primary: SyntaxHighlightStyle
18 predictive: SyntaxHighlightStyle
19
20 // === Formatted Text ====== /
21 emphasis: SyntaxHighlightStyle
22 "emphasis.strong": SyntaxHighlightStyle
23 title: SyntaxHighlightStyle
24 linkUri: SyntaxHighlightStyle
25 linkText: SyntaxHighlightStyle
26 /** md: indented_code_block, fenced_code_block, code_span */
27 "text.literal": SyntaxHighlightStyle
28
29 // == Punctuation ====== /
30 punctuation: SyntaxHighlightStyle
31 /** Example: `(`, `[`, `{`...*/
32 "punctuation.bracket": SyntaxHighlightStyle
33 /**., ;*/
34 "punctuation.delimiter": SyntaxHighlightStyle
35 // js, ts: ${, } in a template literal
36 // yaml: *, &, ---, ...
37 "punctuation.special": SyntaxHighlightStyle
38 // md: list_marker_plus, list_marker_dot, etc
39 "punctuation.list_marker": SyntaxHighlightStyle
40
41 // == Strings ====== /
42
43 string: SyntaxHighlightStyle
44 // css: color_value
45 // js: this, super
46 // toml: offset_date_time, local_date_time...
47 "string.special": SyntaxHighlightStyle
48 // elixir: atom, quoted_atom, keyword, quoted_keyword
49 // ruby: simple_symbol, delimited_symbol...
50 "string.special.symbol": SyntaxHighlightStyle
51 // elixir, python, yaml...: escape_sequence
52 "string.escape": SyntaxHighlightStyle
53 // Regular expressions
54 "string.regex": SyntaxHighlightStyle
55
56 // == Types ====== /
57 // We allow Function here because all JS objects literals have this property
58 constructor: SyntaxHighlightStyle | Function
59 variant: SyntaxHighlightStyle
60 type: SyntaxHighlightStyle
61 // js: predefined_type
62 "type.builtin": SyntaxHighlightStyle
63
64 // == Values
65 variable: SyntaxHighlightStyle
66 // racket: lang_name
67 "variable.builtin": SyntaxHighlightStyle
68 // this, ...
69 // css: -- (var(--foo))
70 // lua: self
71 "variable.special": SyntaxHighlightStyle
72 // c: statement_identifier,
73 label: SyntaxHighlightStyle
74 // css: tag_name, nesting_selector, universal_selector...
75 tag: SyntaxHighlightStyle
76 // css: attribute, pseudo_element_selector (tag_name),
77 attribute: SyntaxHighlightStyle
78 // css: class_name, property_name, namespace_name...
79 property: SyntaxHighlightStyle
80 // true, false, null, nullptr
81 constant: SyntaxHighlightStyle
82 // css: @media, @import, @supports...
83 // js: declare, implements, interface, keyof, public...
84 keyword: SyntaxHighlightStyle
85 // note: js enum is currently defined as a keyword
86 enum: SyntaxHighlightStyle
87 // -, --, ->, !=, &&, ||, <=...
88 operator: SyntaxHighlightStyle
89 number: SyntaxHighlightStyle
90 boolean: SyntaxHighlightStyle
91 // elixir: __MODULE__, __DIR__, __ENV__, etc
92 // go: nil, iota
93 "constant.builtin": SyntaxHighlightStyle
94
95 // == Functions ====== /
96
97 function: SyntaxHighlightStyle
98 // lua: assert, error, loadfile, tostring, unpack...
99 "function.builtin": SyntaxHighlightStyle
100 // lua: function_call
101 "function.call": SyntaxHighlightStyle
102 // go: call_expression, method_declaration
103 // js: call_expression, method_definition, pair (key, arrow function)
104 // rust: function_item name: (identifier)
105 "function.definition": SyntaxHighlightStyle
106 // rust: macro_definition name: (identifier)
107 "function.special.definition": SyntaxHighlightStyle
108 "function.method": SyntaxHighlightStyle
109 // ruby: identifier/"defined?" // Nate note: I don't fully understand this one.
110 "function.method.builtin": SyntaxHighlightStyle
111
112 // == Unsorted ====== /
113 // lua: hash_bang_line
114 preproc: SyntaxHighlightStyle
115 // elixir, python: interpolation (ex: foo in ${foo})
116 // js: template_substitution
117 embedded: SyntaxHighlightStyle
118}
119
120// HACK: "constructor" as a key in the syntax interface returns an error when a theme tries to use it.
121// For now hack around it by omiting constructor as a valid key for overrides.
122// export type ThemeSyntax = Partial<Omit<Syntax, "constructor">>
123export type ThemeSyntax = Partial<Syntax>
124
125const defaultSyntaxHighlightStyle: Omit<SyntaxHighlightStyle, "color"> = {
126 weight: fontWeights.normal,
127 underline: false,
128 italic: false,
129}
130
131function buildDefaultSyntax(colorScheme: ColorScheme): Syntax {
132 // Make a temporary object that is allowed to be missing
133 // the "color" property for each style
134 const syntax: {
135 [key: string]: Omit<SyntaxHighlightStyle, "color">
136 } = {}
137
138 // then spread the default to each style
139 for (const key of Object.keys({} as Syntax)) {
140 syntax[key as keyof Syntax] = {
141 ...defaultSyntaxHighlightStyle,
142 }
143 }
144
145 const color = {
146 primary: colorScheme.ramps.neutral(1).hex(),
147 comment: colorScheme.ramps.neutral(0.71).hex(),
148 punctuation: colorScheme.ramps.neutral(0.86).hex(),
149 predictive: colorScheme.ramps.neutral(0.57).hex(),
150 emphasis: colorScheme.ramps.blue(0.5).hex(),
151 string: colorScheme.ramps.orange(0.5).hex(),
152 function: colorScheme.ramps.yellow(0.5).hex(),
153 type: colorScheme.ramps.cyan(0.5).hex(),
154 constructor: colorScheme.ramps.blue(0.5).hex(),
155 variant: colorScheme.ramps.blue(0.5).hex(),
156 property: colorScheme.ramps.blue(0.5).hex(),
157 enum: colorScheme.ramps.orange(0.5).hex(),
158 operator: colorScheme.ramps.orange(0.5).hex(),
159 number: colorScheme.ramps.green(0.5).hex(),
160 boolean: colorScheme.ramps.green(0.5).hex(),
161 constant: colorScheme.ramps.green(0.5).hex(),
162 keyword: colorScheme.ramps.blue(0.5).hex(),
163 }
164
165 // Then assign colors and use Syntax to enforce each style getting it's own color
166 const defaultSyntax: Syntax = {
167 ...syntax,
168 comment: {
169 color: color.comment,
170 },
171 "comment.doc": {
172 color: color.comment,
173 },
174 primary: {
175 color: color.primary,
176 },
177 predictive: {
178 color: color.predictive,
179 },
180 emphasis: {
181 color: color.emphasis,
182 },
183 "emphasis.strong": {
184 color: color.emphasis,
185 weight: fontWeights.bold,
186 },
187 title: {
188 color: color.primary,
189 weight: fontWeights.bold,
190 },
191 linkUri: {
192 color: colorScheme.ramps.green(0.5).hex(),
193 underline: true,
194 },
195 linkText: {
196 color: colorScheme.ramps.orange(0.5).hex(),
197 italic: true,
198 },
199 "text.literal": {
200 color: color.string,
201 },
202 punctuation: {
203 color: color.punctuation,
204 },
205 "punctuation.bracket": {
206 color: color.punctuation,
207 },
208 "punctuation.delimiter": {
209 color: color.punctuation,
210 },
211 "punctuation.special": {
212 color: colorScheme.ramps.neutral(0.86).hex(),
213 },
214 "punctuation.list_marker": {
215 color: color.punctuation,
216 },
217 string: {
218 color: color.string,
219 },
220 "string.special": {
221 color: color.string,
222 },
223 "string.special.symbol": {
224 color: color.string,
225 },
226 "string.escape": {
227 color: color.comment,
228 },
229 "string.regex": {
230 color: color.string,
231 },
232 constructor: {
233 color: colorScheme.ramps.blue(0.5).hex(),
234 },
235 variant: {
236 color: colorScheme.ramps.blue(0.5).hex(),
237 },
238 type: {
239 color: color.type,
240 },
241 "type.builtin": {
242 color: color.type,
243 },
244 variable: {
245 color: color.primary,
246 },
247 "variable.builtin": {
248 color: colorScheme.ramps.blue(0.5).hex(),
249 },
250 "variable.special": {
251 color: colorScheme.ramps.blue(0.7).hex(),
252 },
253 label: {
254 color: colorScheme.ramps.blue(0.5).hex(),
255 },
256 tag: {
257 color: colorScheme.ramps.blue(0.5).hex(),
258 },
259 attribute: {
260 color: colorScheme.ramps.blue(0.5).hex(),
261 },
262 property: {
263 color: colorScheme.ramps.blue(0.5).hex(),
264 },
265 constant: {
266 color: color.constant,
267 },
268 keyword: {
269 color: color.keyword,
270 },
271 enum: {
272 color: color.enum,
273 },
274 operator: {
275 color: color.operator,
276 },
277 number: {
278 color: color.number,
279 },
280 boolean: {
281 color: color.boolean,
282 },
283 "constant.builtin": {
284 color: color.constant,
285 },
286 function: {
287 color: color.function,
288 },
289 "function.builtin": {
290 color: color.function,
291 },
292 "function.call": {
293 color: color.function,
294 },
295 "function.definition": {
296 color: color.function,
297 },
298 "function.special.definition": {
299 color: color.function,
300 },
301 "function.method": {
302 color: color.function,
303 },
304 "function.method.builtin": {
305 color: color.function,
306 },
307 preproc: {
308 color: color.primary,
309 },
310 embedded: {
311 color: color.primary,
312 },
313 }
314
315 return defaultSyntax
316}
317
318function mergeSyntax(defaultSyntax: Syntax, colorScheme: ColorScheme): Syntax {
319 if (!colorScheme.syntax) {
320 return defaultSyntax
321 }
322
323 return deepmerge<Syntax, Partial<ThemeSyntax>>(
324 defaultSyntax,
325 colorScheme.syntax,
326 {
327 arrayMerge: (destinationArray, sourceArray) => [
328 ...destinationArray,
329 ...sourceArray,
330 ],
331 }
332 )
333}
334
335export function buildSyntax(colorScheme: ColorScheme): Syntax {
336 const defaultSyntax: Syntax = buildDefaultSyntax(colorScheme)
337
338 const syntax = mergeSyntax(defaultSyntax, colorScheme)
339
340 return syntax
341}