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 // this, ...
67 // css: -- (var(--foo))
68 // lua: self
69 "variable.special"?: SyntaxHighlightStyle
70 // c: statement_identifier,
71 label: SyntaxHighlightStyle
72 // css: tag_name, nesting_selector, universal_selector...
73 tag: SyntaxHighlightStyle
74 // css: attribute, pseudo_element_selector (tag_name),
75 attribute: SyntaxHighlightStyle
76 // css: class_name, property_name, namespace_name...
77 property: SyntaxHighlightStyle
78 // true, false, null, nullptr
79 constant: SyntaxHighlightStyle
80 // css: @media, @import, @supports...
81 // js: declare, implements, interface, keyof, public...
82 keyword: SyntaxHighlightStyle
83 // note: js enum is currently defined as a keyword
84 enum: SyntaxHighlightStyle
85 // -, --, ->, !=, &&, ||, <=...
86 operator: SyntaxHighlightStyle
87 number: SyntaxHighlightStyle
88 boolean: SyntaxHighlightStyle
89 // elixir: __MODULE__, __DIR__, __ENV__, etc
90 // go: nil, iota
91 "constant.builtin"?: SyntaxHighlightStyle
92
93 // == Functions ====== /
94
95 function: SyntaxHighlightStyle
96 // lua: assert, error, loadfile, tostring, unpack...
97 "function.builtin"?: SyntaxHighlightStyle
98 // go: call_expression, method_declaration
99 // js: call_expression, method_definition, pair (key, arrow function)
100 // rust: function_item name: (identifier)
101 "function.definition"?: SyntaxHighlightStyle
102 // rust: macro_definition name: (identifier)
103 "function.special.definition"?: SyntaxHighlightStyle
104 "function.method"?: SyntaxHighlightStyle
105 // ruby: identifier/"defined?" // Nate note: I don't fully understand this one.
106 "function.method.builtin"?: SyntaxHighlightStyle
107
108 // == Unsorted ====== /
109 // lua: hash_bang_line
110 preproc: SyntaxHighlightStyle
111 // elixir, python: interpolation (ex: foo in ${foo})
112 // js: template_substitution
113 embedded: SyntaxHighlightStyle
114}
115
116export type ThemeSyntax = Partial<Syntax>
117
118const defaultSyntaxHighlightStyle: Omit<SyntaxHighlightStyle, "color"> = {
119 weight: fontWeights.normal,
120 underline: false,
121 italic: false,
122}
123
124function buildDefaultSyntax(colorScheme: ColorScheme): Syntax {
125 // Make a temporary object that is allowed to be missing
126 // the "color" property for each style
127 const syntax: {
128 [key: string]: Omit<SyntaxHighlightStyle, "color">
129 } = {}
130
131 // then spread the default to each style
132 for (const key of Object.keys({} as Syntax)) {
133 syntax[key as keyof Syntax] = {
134 ...defaultSyntaxHighlightStyle,
135 }
136 }
137
138 const color = {
139 primary: colorScheme.ramps.neutral(1).hex(),
140 comment: colorScheme.ramps.neutral(0.71).hex(),
141 punctuation: colorScheme.ramps.neutral(0.86).hex(),
142 predictive: colorScheme.ramps.neutral(0.57).hex(),
143 emphasis: colorScheme.ramps.blue(0.5).hex(),
144 string: colorScheme.ramps.orange(0.5).hex(),
145 function: colorScheme.ramps.yellow(0.5).hex(),
146 type: colorScheme.ramps.cyan(0.5).hex(),
147 constructor: colorScheme.ramps.blue(0.5).hex(),
148 variant: colorScheme.ramps.blue(0.5).hex(),
149 property: colorScheme.ramps.blue(0.5).hex(),
150 enum: colorScheme.ramps.orange(0.5).hex(),
151 operator: colorScheme.ramps.orange(0.5).hex(),
152 number: colorScheme.ramps.green(0.5).hex(),
153 boolean: colorScheme.ramps.green(0.5).hex(),
154 constant: colorScheme.ramps.green(0.5).hex(),
155 keyword: colorScheme.ramps.blue(0.5).hex(),
156 }
157
158 // Then assign colors and use Syntax to enforce each style getting it's own color
159 const defaultSyntax: Syntax = {
160 ...syntax,
161 comment: {
162 color: color.comment,
163 },
164 "comment.doc": {
165 color: color.comment,
166 },
167 primary: {
168 color: color.primary,
169 },
170 predictive: {
171 color: color.predictive,
172 },
173 emphasis: {
174 color: color.emphasis,
175 },
176 "emphasis.strong": {
177 color: color.emphasis,
178 weight: fontWeights.bold,
179 },
180 title: {
181 color: color.primary,
182 weight: fontWeights.bold,
183 },
184 linkUri: {
185 color: colorScheme.ramps.green(0.5).hex(),
186 underline: true,
187 },
188 linkText: {
189 color: colorScheme.ramps.orange(0.5).hex(),
190 italic: true,
191 },
192 "text.literal": {
193 color: color.string,
194 },
195 punctuation: {
196 color: color.punctuation,
197 },
198 "punctuation.bracket": {
199 color: color.punctuation,
200 },
201 "punctuation.delimiter": {
202 color: color.punctuation,
203 },
204 "punctuation.special": {
205 color: colorScheme.ramps.neutral(0.86).hex(),
206 },
207 "punctuation.list_marker": {
208 color: color.punctuation,
209 },
210 string: {
211 color: color.string,
212 },
213 "string.special": {
214 color: color.string,
215 },
216 "string.special.symbol": {
217 color: color.string,
218 },
219 "string.escape": {
220 color: color.comment,
221 },
222 "string.regex": {
223 color: color.string,
224 },
225 constructor: {
226 color: colorScheme.ramps.blue(0.5).hex(),
227 },
228 variant: {
229 color: colorScheme.ramps.blue(0.5).hex(),
230 },
231 type: {
232 color: color.type,
233 },
234 variable: {
235 color: color.primary,
236 },
237 label: {
238 color: colorScheme.ramps.blue(0.5).hex(),
239 },
240 tag: {
241 color: colorScheme.ramps.blue(0.5).hex(),
242 },
243 attribute: {
244 color: colorScheme.ramps.blue(0.5).hex(),
245 },
246 property: {
247 color: colorScheme.ramps.blue(0.5).hex(),
248 },
249 constant: {
250 color: color.constant,
251 },
252 keyword: {
253 color: color.keyword,
254 },
255 enum: {
256 color: color.enum,
257 },
258 operator: {
259 color: color.operator,
260 },
261 number: {
262 color: color.number,
263 },
264 boolean: {
265 color: color.boolean,
266 },
267 function: {
268 color: color.function,
269 },
270 preproc: {
271 color: color.primary,
272 },
273 embedded: {
274 color: color.primary,
275 },
276 }
277
278 return defaultSyntax
279}
280
281function mergeSyntax(defaultSyntax: Syntax, colorScheme: ColorScheme): Syntax {
282 if (!colorScheme.syntax) {
283 return defaultSyntax
284 }
285
286 return deepmerge<Syntax, Partial<ThemeSyntax>>(
287 defaultSyntax,
288 colorScheme.syntax,
289 {
290 arrayMerge: (destinationArray, sourceArray) => [
291 ...destinationArray,
292 ...sourceArray,
293 ],
294 }
295 )
296}
297
298export function buildSyntax(colorScheme: ColorScheme): Syntax {
299 const defaultSyntax: Syntax = buildDefaultSyntax(colorScheme)
300
301 const syntax = mergeSyntax(defaultSyntax, colorScheme)
302
303 return syntax
304}