import deepmerge from "deepmerge"import { FontWeight, font_weights, useTheme } from "../common"import chroma from "chroma-js" export interface SyntaxHighlightStyle { color?: string weight?: FontWeight underline?: boolean italic?: boolean} export interface Syntax { // == Text Styles ====== / comment: SyntaxHighlightStyle // elixir: doc comment "comment.doc": SyntaxHighlightStyle primary: SyntaxHighlightStyle predictive: SyntaxHighlightStyle hint: SyntaxHighlightStyle // === Formatted Text ====== / emphasis: SyntaxHighlightStyle "emphasis.strong": SyntaxHighlightStyle title: SyntaxHighlightStyle link_uri: SyntaxHighlightStyle link_text: SyntaxHighlightStyle /** md: indented_code_block, fenced_code_block, code_span */ "text.literal": SyntaxHighlightStyle // == Punctuation ====== / punctuation: SyntaxHighlightStyle /** Example: `(`, `[`, `{`...*/ "punctuation.bracket": SyntaxHighlightStyle /**., ;*/ "punctuation.delimiter": SyntaxHighlightStyle // js, ts: ${, } in a template literal // yaml: *, &, ---, ... "punctuation.special": SyntaxHighlightStyle // md: list_marker_plus, list_marker_dot, etc "punctuation.list_marker": SyntaxHighlightStyle // == Strings ====== / string: SyntaxHighlightStyle // css: color_value // js: this, super // toml: offset_date_time, local_date_time... "string.special": SyntaxHighlightStyle // elixir: atom, quoted_atom, keyword, quoted_keyword // ruby: simple_symbol, delimited_symbol... "string.special.symbol"?: SyntaxHighlightStyle // elixir, python, yaml...: escape_sequence "string.escape"?: SyntaxHighlightStyle // Regular expressions "string.regex"?: SyntaxHighlightStyle // == Types ====== / // We allow Function here because all JS objects literals have this property constructor: SyntaxHighlightStyle | Function // eslint-disable-line @typescript-eslint/ban-types variant: SyntaxHighlightStyle type: SyntaxHighlightStyle // js: predefined_type "type.builtin"?: SyntaxHighlightStyle // == Values variable: SyntaxHighlightStyle // this, ... // css: -- (var(--foo)) // lua: self "variable.special"?: SyntaxHighlightStyle // c: statement_identifier, label: SyntaxHighlightStyle // css: tag_name, nesting_selector, universal_selector... tag: SyntaxHighlightStyle // css: attribute, pseudo_element_selector (tag_name), attribute: SyntaxHighlightStyle // css: class_name, property_name, namespace_name... property: SyntaxHighlightStyle // true, false, null, nullptr constant: SyntaxHighlightStyle // css: @media, @import, @supports... // js: declare, implements, interface, keyof, public... keyword: SyntaxHighlightStyle // note: js enum is currently defined as a keyword enum: SyntaxHighlightStyle // -, --, ->, !=, &&, ||, <=... operator: SyntaxHighlightStyle number: SyntaxHighlightStyle boolean: SyntaxHighlightStyle // elixir: __MODULE__, __DIR__, __ENV__, etc // go: nil, iota "constant.builtin"?: SyntaxHighlightStyle // == Functions ====== / function: SyntaxHighlightStyle // lua: assert, error, loadfile, tostring, unpack... "function.builtin"?: SyntaxHighlightStyle // go: call_expression, method_declaration // js: call_expression, method_definition, pair (key, arrow function) // rust: function_item name: (identifier) "function.definition"?: SyntaxHighlightStyle // rust: macro_definition name: (identifier) "function.special.definition"?: SyntaxHighlightStyle "function.method"?: SyntaxHighlightStyle // ruby: identifier/"defined?" // Nate note: I don't fully understand this one. "function.method.builtin"?: SyntaxHighlightStyle // == Unsorted ====== / // lua: hash_bang_line preproc: SyntaxHighlightStyle // elixir, python: interpolation (ex: foo in ${foo}) // js: template_substitution embedded: SyntaxHighlightStyle} export type ThemeSyntax = Partial<Syntax> const default_syntax_highlight_style: Omit<SyntaxHighlightStyle, "color"> = { weight: "normal", underline: false, italic: false,} function build_default_syntax(): Syntax { const theme = useTheme() // Make a temporary object that is allowed to be missing // the "color" property for each style const syntax: { [key: string]: Omit<SyntaxHighlightStyle, "color"> } = {} // then spread the default to each style for (const key of Object.keys({} as Syntax)) { syntax[key as keyof Syntax] = { ...default_syntax_highlight_style, } } // Mix the neutral and blue colors to get a // predictive color distinct from any other color in the theme const predictive = chroma .mix( theme.ramps.neutral(0.4).hex(), theme.ramps.blue(0.4).hex(), 0.45, "lch" ) .hex() // Mix the neutral and green colors to get a // hint color distinct from any other color in the theme const hint = chroma .mix( theme.ramps.neutral(0.6).hex(), theme.ramps.blue(0.4).hex(), 0.45, "lch" ) .hex() const color = { primary: theme.ramps.neutral(1).hex(), comment: theme.ramps.neutral(0.71).hex(), punctuation: theme.ramps.neutral(0.86).hex(), predictive: predictive, hint: hint, emphasis: theme.ramps.blue(0.5).hex(), string: theme.ramps.orange(0.5).hex(), function: theme.ramps.yellow(0.5).hex(), type: theme.ramps.cyan(0.5).hex(), constructor: theme.ramps.blue(0.5).hex(), variant: theme.ramps.blue(0.5).hex(), property: theme.ramps.blue(0.5).hex(), enum: theme.ramps.orange(0.5).hex(), operator: theme.ramps.orange(0.5).hex(), number: theme.ramps.green(0.5).hex(), boolean: theme.ramps.green(0.5).hex(), constant: theme.ramps.green(0.5).hex(), keyword: theme.ramps.blue(0.5).hex(), } // Then assign colors and use Syntax to enforce each style getting it's own color const default_syntax: Syntax = { ...syntax, comment: { color: color.comment, }, "comment.doc": { color: color.comment, }, primary: { color: color.primary, }, predictive: { color: color.predictive, italic: true, }, hint: { color: color.hint, weight: font_weights.bold, }, emphasis: { color: color.emphasis, }, "emphasis.strong": { color: color.emphasis, weight: font_weights.bold, }, title: { color: color.primary, weight: font_weights.bold, }, link_uri: { color: theme.ramps.green(0.5).hex(), underline: true, }, link_text: { color: theme.ramps.orange(0.5).hex(), italic: true, }, "text.literal": { color: color.string, }, punctuation: { color: color.punctuation, }, "punctuation.bracket": { color: color.punctuation, }, "punctuation.delimiter": { color: color.punctuation, }, "punctuation.special": { color: theme.ramps.neutral(0.86).hex(), }, "punctuation.list_marker": { color: color.punctuation, }, string: { color: color.string, }, "string.special": { color: color.string, }, "string.special.symbol": { color: color.string, }, "string.escape": { color: color.comment, }, "string.regex": { color: color.string, }, constructor: { color: theme.ramps.blue(0.5).hex(), }, variant: { color: theme.ramps.blue(0.5).hex(), }, type: { color: color.type, }, variable: { color: color.primary, }, label: { color: theme.ramps.blue(0.5).hex(), }, tag: { color: theme.ramps.blue(0.5).hex(), }, attribute: { color: theme.ramps.blue(0.5).hex(), }, property: { color: theme.ramps.blue(0.5).hex(), }, constant: { color: color.constant, }, keyword: { color: color.keyword, }, enum: { color: color.enum, }, operator: { color: color.operator, }, number: { color: color.number, }, boolean: { color: color.boolean, }, function: { color: color.function, }, preproc: { color: color.primary, }, embedded: { color: color.primary, }, } return default_syntax} export function build_syntax(): Syntax { const theme = useTheme() const default_syntax: Syntax = build_default_syntax() if (!theme.syntax) { return default_syntax } const syntax = deepmerge<Syntax, Partial<ThemeSyntax>>( default_syntax, theme.syntax, { arrayMerge: (destinationArray, sourceArray) => [ ...destinationArray, ...sourceArray, ], } ) return syntax}
import deepmerge from "deepmerge"
import { FontWeight, font_weights, useTheme } from "../common"
import chroma from "chroma-js"
export interface SyntaxHighlightStyle {
color?: string
weight?: FontWeight
underline?: boolean
italic?: boolean
}
export interface Syntax {
// == Text Styles ====== /
comment: SyntaxHighlightStyle
// elixir: doc comment
"comment.doc": SyntaxHighlightStyle
primary: SyntaxHighlightStyle
predictive: SyntaxHighlightStyle
hint: SyntaxHighlightStyle
// === Formatted Text ====== /
emphasis: SyntaxHighlightStyle
"emphasis.strong": SyntaxHighlightStyle
title: SyntaxHighlightStyle
link_uri: SyntaxHighlightStyle
link_text: SyntaxHighlightStyle
/** md: indented_code_block, fenced_code_block, code_span */
"text.literal": SyntaxHighlightStyle
// == Punctuation ====== /
punctuation: SyntaxHighlightStyle
/** Example: `(`, `[`, `{`...*/
"punctuation.bracket": SyntaxHighlightStyle
/**., ;*/
"punctuation.delimiter": SyntaxHighlightStyle
// js, ts: ${, } in a template literal
// yaml: *, &, ---, ...
"punctuation.special": SyntaxHighlightStyle
// md: list_marker_plus, list_marker_dot, etc
"punctuation.list_marker": SyntaxHighlightStyle
// == Strings ====== /
string: SyntaxHighlightStyle
// css: color_value
// js: this, super
// toml: offset_date_time, local_date_time...
"string.special": SyntaxHighlightStyle
// elixir: atom, quoted_atom, keyword, quoted_keyword
// ruby: simple_symbol, delimited_symbol...
"string.special.symbol"?: SyntaxHighlightStyle
// elixir, python, yaml...: escape_sequence
"string.escape"?: SyntaxHighlightStyle
// Regular expressions
"string.regex"?: SyntaxHighlightStyle
// == Types ====== /
// We allow Function here because all JS objects literals have this property
constructor: SyntaxHighlightStyle | Function // eslint-disable-line @typescript-eslint/ban-types
variant: SyntaxHighlightStyle
type: SyntaxHighlightStyle
// js: predefined_type
"type.builtin"?: SyntaxHighlightStyle
// == Values
variable: SyntaxHighlightStyle
// this, ...
// css: -- (var(--foo))
// lua: self
"variable.special"?: SyntaxHighlightStyle
// c: statement_identifier,
label: SyntaxHighlightStyle
// css: tag_name, nesting_selector, universal_selector...
tag: SyntaxHighlightStyle
// css: attribute, pseudo_element_selector (tag_name),
attribute: SyntaxHighlightStyle
// css: class_name, property_name, namespace_name...
property: SyntaxHighlightStyle
// true, false, null, nullptr
constant: SyntaxHighlightStyle
// css: @media, @import, @supports...
// js: declare, implements, interface, keyof, public...
keyword: SyntaxHighlightStyle
// note: js enum is currently defined as a keyword
enum: SyntaxHighlightStyle
// -, --, ->, !=, &&, ||, <=...
operator: SyntaxHighlightStyle
number: SyntaxHighlightStyle
boolean: SyntaxHighlightStyle
// elixir: __MODULE__, __DIR__, __ENV__, etc
// go: nil, iota
"constant.builtin"?: SyntaxHighlightStyle
// == Functions ====== /
function: SyntaxHighlightStyle
// lua: assert, error, loadfile, tostring, unpack...
"function.builtin"?: SyntaxHighlightStyle
// go: call_expression, method_declaration
// js: call_expression, method_definition, pair (key, arrow function)
// rust: function_item name: (identifier)
"function.definition"?: SyntaxHighlightStyle
// rust: macro_definition name: (identifier)
"function.special.definition"?: SyntaxHighlightStyle
"function.method"?: SyntaxHighlightStyle
// ruby: identifier/"defined?" // Nate note: I don't fully understand this one.
"function.method.builtin"?: SyntaxHighlightStyle
// == Unsorted ====== /
// lua: hash_bang_line
preproc: SyntaxHighlightStyle
// elixir, python: interpolation (ex: foo in ${foo})
// js: template_substitution
embedded: SyntaxHighlightStyle
}
export type ThemeSyntax = Partial<Syntax>
const default_syntax_highlight_style: Omit<SyntaxHighlightStyle, "color"> = {
weight: "normal",
underline: false,
italic: false,
}
function build_default_syntax(): Syntax {
const theme = useTheme()
// Make a temporary object that is allowed to be missing
// the "color" property for each style
const syntax: {
[key: string]: Omit<SyntaxHighlightStyle, "color">
} = {}
// then spread the default to each style
for (const key of Object.keys({} as Syntax)) {
syntax[key as keyof Syntax] = {
...default_syntax_highlight_style,
}
}
// Mix the neutral and blue colors to get a
// predictive color distinct from any other color in the theme
const predictive = chroma
.mix(
theme.ramps.neutral(0.4).hex(),
theme.ramps.blue(0.4).hex(),
0.45,
"lch"
)
.hex()
// Mix the neutral and green colors to get a
// hint color distinct from any other color in the theme
const hint = chroma
.mix(
theme.ramps.neutral(0.6).hex(),
theme.ramps.blue(0.4).hex(),
0.45,
"lch"
)
.hex()
const color = {
primary: theme.ramps.neutral(1).hex(),
comment: theme.ramps.neutral(0.71).hex(),
punctuation: theme.ramps.neutral(0.86).hex(),
predictive: predictive,
hint: hint,
emphasis: theme.ramps.blue(0.5).hex(),
string: theme.ramps.orange(0.5).hex(),
function: theme.ramps.yellow(0.5).hex(),
type: theme.ramps.cyan(0.5).hex(),
constructor: theme.ramps.blue(0.5).hex(),
variant: theme.ramps.blue(0.5).hex(),
property: theme.ramps.blue(0.5).hex(),
enum: theme.ramps.orange(0.5).hex(),
operator: theme.ramps.orange(0.5).hex(),
number: theme.ramps.green(0.5).hex(),
boolean: theme.ramps.green(0.5).hex(),
constant: theme.ramps.green(0.5).hex(),
keyword: theme.ramps.blue(0.5).hex(),
}
// Then assign colors and use Syntax to enforce each style getting it's own color
const default_syntax: Syntax = {
...syntax,
comment: {
color: color.comment,
},
"comment.doc": {
color: color.comment,
},
primary: {
color: color.primary,
},
predictive: {
color: color.predictive,
italic: true,
},
hint: {
color: color.hint,
weight: font_weights.bold,
},
emphasis: {
color: color.emphasis,
},
"emphasis.strong": {
color: color.emphasis,
weight: font_weights.bold,
},
title: {
color: color.primary,
weight: font_weights.bold,
},
link_uri: {
color: theme.ramps.green(0.5).hex(),
underline: true,
},
link_text: {
color: theme.ramps.orange(0.5).hex(),
italic: true,
},
"text.literal": {
color: color.string,
},
punctuation: {
color: color.punctuation,
},
"punctuation.bracket": {
color: color.punctuation,
},
"punctuation.delimiter": {
color: color.punctuation,
},
"punctuation.special": {
color: theme.ramps.neutral(0.86).hex(),
},
"punctuation.list_marker": {
color: color.punctuation,
},
string: {
color: color.string,
},
"string.special": {
color: color.string,
},
"string.special.symbol": {
color: color.string,
},
"string.escape": {
color: color.comment,
},
"string.regex": {
color: color.string,
},
constructor: {
color: theme.ramps.blue(0.5).hex(),
},
variant: {
color: theme.ramps.blue(0.5).hex(),
},
type: {
color: color.type,
},
variable: {
color: color.primary,
},
label: {
color: theme.ramps.blue(0.5).hex(),
},
tag: {
color: theme.ramps.blue(0.5).hex(),
},
attribute: {
color: theme.ramps.blue(0.5).hex(),
},
property: {
color: theme.ramps.blue(0.5).hex(),
},
constant: {
color: color.constant,
},
keyword: {
color: color.keyword,
},
enum: {
color: color.enum,
},
operator: {
color: color.operator,
},
number: {
color: color.number,
},
boolean: {
color: color.boolean,
},
function: {
color: color.function,
},
preproc: {
color: color.primary,
},
embedded: {
color: color.primary,
},
}
return default_syntax
}
export function build_syntax(): Syntax {
const theme = useTheme()
const default_syntax: Syntax = build_default_syntax()
if (!theme.syntax) {
return default_syntax
}
const syntax = deepmerge<Syntax, Partial<ThemeSyntax>>(
default_syntax,
theme.syntax,
{
arrayMerge: (destinationArray, sourceArray) => [
...destinationArray,
...sourceArray,
],
}
)
return syntax
}