1import deepmerge from "deepmerge"
2import { FontWeight, fontWeights } from "../../common"
3import {
4 ColorScheme,
5} from "./colorScheme"
6
7export interface SyntaxHighlightStyle {
8 color: string
9 weight?: FontWeight
10 underline?: boolean
11 italic?: boolean
12}
13
14export interface Syntax {
15 // == Text Styles ====== /
16 comment: SyntaxHighlightStyle
17 // elixir: doc comment
18 "comment.doc": SyntaxHighlightStyle
19 primary: SyntaxHighlightStyle
20 predictive: SyntaxHighlightStyle
21
22 // === Formatted Text ====== /
23 emphasis: SyntaxHighlightStyle
24 "emphasis.strong": SyntaxHighlightStyle
25 title: SyntaxHighlightStyle
26 linkUri: SyntaxHighlightStyle
27 linkText: SyntaxHighlightStyle
28 /** md: indented_code_block, fenced_code_block, code_span */
29 "text.literal": SyntaxHighlightStyle
30
31 // == Punctuation ====== /
32 punctuation: SyntaxHighlightStyle
33 /** Example: `(`, `[`, `{`...*/
34 "punctuation.bracket": SyntaxHighlightStyle
35 /**., ;*/
36 "punctuation.delimiter": SyntaxHighlightStyle
37 // js, ts: ${, } in a template literal
38 // yaml: *, &, ---, ...
39 "punctuation.special": SyntaxHighlightStyle
40 // md: list_marker_plus, list_marker_dot, etc
41 "punctuation.list_marker": SyntaxHighlightStyle
42
43 // == Strings ====== /
44
45 string: SyntaxHighlightStyle
46 // css: color_value
47 // js: this, super
48 // toml: offset_date_time, local_date_time...
49 "string.special": SyntaxHighlightStyle
50 // elixir: atom, quoted_atom, keyword, quoted_keyword
51 // ruby: simple_symbol, delimited_symbol...
52 "string.special.symbol": SyntaxHighlightStyle
53 // elixir, python, yaml...: escape_sequence
54 "string.escape": SyntaxHighlightStyle
55 // Regular expressions
56 "string.regex": SyntaxHighlightStyle
57
58 // == Types ====== /
59 constructor: SyntaxHighlightStyle
60 variant: SyntaxHighlightStyle
61 type: SyntaxHighlightStyle
62 // js: predefined_type
63 "type.builtin": SyntaxHighlightStyle
64
65 // == Values
66 variable: SyntaxHighlightStyle
67 // racket: lang_name
68 "variable.builtin": SyntaxHighlightStyle
69 // this, ...
70 // css: -- (var(--foo))
71 // lua: self
72 "variable.special": SyntaxHighlightStyle
73 // c: statement_identifier,
74 label: SyntaxHighlightStyle
75 // css: tag_name, nesting_selector, universal_selector...
76 tag: SyntaxHighlightStyle
77 // css: attribute, pseudo_element_selector (tag_name),
78 attribute: SyntaxHighlightStyle
79 // css: class_name, property_name, namespace_name...
80 property: SyntaxHighlightStyle
81 // true, false, null, nullptr
82 constant: SyntaxHighlightStyle
83 // css: @media, @import, @supports...
84 // js: declare, implements, interface, keyof, public...
85 keyword: SyntaxHighlightStyle
86 // note: js enum is currently defined as a keyword
87 enum: SyntaxHighlightStyle
88 // -, --, ->, !=, &&, ||, <=...
89 operator: SyntaxHighlightStyle
90 number: SyntaxHighlightStyle
91 boolean: SyntaxHighlightStyle
92 // elixir: __MODULE__, __DIR__, __ENV__, etc
93 // go: nil, iota
94 "constant.builtin": SyntaxHighlightStyle
95
96 // == Functions ====== /
97
98 function: SyntaxHighlightStyle
99 // lua: assert, error, loadfile, tostring, unpack...
100 "function.builtin": SyntaxHighlightStyle
101 // lua: function_call
102 "function.call": SyntaxHighlightStyle
103 // go: call_expression, method_declaration
104 // js: call_expression, method_definition, pair (key, arrow function)
105 // rust: function_item name: (identifier)
106 "function.definition": SyntaxHighlightStyle
107 // rust: macro_definition name: (identifier)
108 "function.special.definition": SyntaxHighlightStyle
109 "function.method": SyntaxHighlightStyle
110 // ruby: identifier/"defined?" // Nate note: I don't fully understand this one.
111 "function.method.builtin": SyntaxHighlightStyle
112
113 // == Unsorted ====== /
114 // lua: hash_bang_line
115 preproc: SyntaxHighlightStyle
116 // elixir, python: interpolation (ex: foo in ${foo})
117 // js: template_substitution
118 embedded: SyntaxHighlightStyle
119}
120
121// HACK: "constructor" as a key in the syntax interface returns an error when a theme tries to use it.
122// For now hack around it by omiting constructor as a valid key for overrides.
123export type ThemeSyntax = Partial<Omit<Syntax, "constructor">>
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 console.log(JSON.stringify(defaultSyntax, null, 2))
316
317 return defaultSyntax
318}
319
320function mergeSyntax(defaultSyntax: Syntax, colorScheme: ColorScheme): Syntax {
321 if (!colorScheme.syntax) {
322 return defaultSyntax
323 }
324
325 return deepmerge<Syntax, Partial<ThemeSyntax>>(
326 defaultSyntax,
327 colorScheme.syntax,
328 {
329 arrayMerge: (destinationArray, sourceArray) => [
330 ...destinationArray,
331 ...sourceArray,
332 ],
333 }
334 )
335}
336
337export function buildSyntax(colorScheme: ColorScheme): Syntax {
338 const defaultSyntax: Syntax = buildDefaultSyntax(colorScheme)
339
340 const syntax = mergeSyntax(defaultSyntax, colorScheme)
341
342 return syntax
343}