syntax.ts

  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}