Merge pull request #2221 from zed-industries/theme-syntax-overrides

Nate Butler created

Theme syntax overrides 🎉

Change summary

styles/package-lock.json                | 14 +++++
styles/package.json                     |  1 
styles/src/styleTree/editor.ts          | 31 ++++++++++
styles/src/themes/common/colorScheme.ts | 38 +++++++++++++
styles/src/themes/common/ramps.ts       |  5 +
styles/src/themes/one-dark.ts           | 49 +++++++++++++----
styles/src/themes/one-light.ts          | 74 ++++++++++++++++++++------
7 files changed, 180 insertions(+), 32 deletions(-)

Detailed changes

styles/package-lock.json 🔗

@@ -14,6 +14,7 @@
                 "bezier-easing": "^2.1.0",
                 "case-anything": "^2.1.10",
                 "chroma-js": "^2.4.2",
+                "deepmerge": "^4.3.0",
                 "toml": "^3.0.0",
                 "ts-node": "^10.9.1"
             }
@@ -131,6 +132,14 @@
             "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
             "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
         },
+        "node_modules/deepmerge": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz",
+            "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/diff": {
             "version": "4.0.2",
             "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -311,6 +320,11 @@
             "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
             "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
         },
+        "deepmerge": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz",
+            "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og=="
+        },
         "diff": {
             "version": "4.0.2",
             "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",

styles/package.json 🔗

@@ -15,6 +15,7 @@
         "bezier-easing": "^2.1.0",
         "case-anything": "^2.1.10",
         "chroma-js": "^2.4.2",
+        "deepmerge": "^4.3.0",
         "toml": "^3.0.0",
         "ts-node": "^10.9.1"
     },

styles/src/styleTree/editor.ts 🔗

@@ -1,9 +1,17 @@
 import { fontWeights } from "../common"
 import { withOpacity } from "../utils/color"
-import { ColorScheme, Layer, StyleSets } from "../themes/common/colorScheme"
+import {
+    ColorScheme,
+    Layer,
+    StyleSets,
+    Syntax,
+    ThemeSyntax,
+} from "../themes/common/colorScheme"
 import { background, border, borderColor, foreground, text } from "./components"
 import hoverPopover from "./hoverPopover"
 
+import deepmerge from "deepmerge"
+
 export default function editor(colorScheme: ColorScheme) {
     let layer = colorScheme.highest
 
@@ -35,7 +43,7 @@ export default function editor(colorScheme: ColorScheme) {
         }
     }
 
-    const syntax = {
+    const defaultSyntax: Syntax = {
         primary: {
             color: colorScheme.ramps.neutral(1).hex(),
             weight: fontWeights.normal,
@@ -129,6 +137,25 @@ export default function editor(colorScheme: ColorScheme) {
         },
     }
 
+    function createSyntax(colorScheme: ColorScheme): Syntax {
+        if (!colorScheme.syntax) {
+            return defaultSyntax
+        }
+
+        return deepmerge<Syntax, Partial<ThemeSyntax>>(
+            defaultSyntax,
+            colorScheme.syntax,
+            {
+                arrayMerge: (destinationArray, sourceArray) => [
+                    ...destinationArray,
+                    ...sourceArray,
+                ],
+            }
+        )
+    }
+
+    const syntax = createSyntax(colorScheme)
+
     return {
         textColor: syntax.primary.color,
         background: background(layer),

styles/src/themes/common/colorScheme.ts 🔗

@@ -1,4 +1,5 @@
 import { Scale } from "chroma-js"
+import { FontWeight } from "../../common"
 
 export interface ColorScheme {
     name: string
@@ -14,6 +15,7 @@ export interface ColorScheme {
     modalShadow: Shadow
 
     players: Players
+    syntax?: Partial<ThemeSyntax>
 }
 
 export interface Meta {
@@ -98,3 +100,39 @@ export interface Style {
     border: string
     foreground: string
 }
+
+export interface SyntaxHighlightStyle {
+    color: string
+    weight?: FontWeight
+    underline?: boolean
+    italic?: boolean
+}
+
+export interface Syntax {
+    primary: SyntaxHighlightStyle
+    "variable.special": SyntaxHighlightStyle
+    comment: SyntaxHighlightStyle
+    punctuation: SyntaxHighlightStyle
+    constant: SyntaxHighlightStyle
+    keyword: SyntaxHighlightStyle
+    function: SyntaxHighlightStyle
+    type: SyntaxHighlightStyle
+    constructor: SyntaxHighlightStyle
+    variant: SyntaxHighlightStyle
+    property: SyntaxHighlightStyle
+    enum: SyntaxHighlightStyle
+    operator: SyntaxHighlightStyle
+    string: SyntaxHighlightStyle
+    number: SyntaxHighlightStyle
+    boolean: SyntaxHighlightStyle
+    predictive: SyntaxHighlightStyle
+    title: SyntaxHighlightStyle
+    emphasis: SyntaxHighlightStyle
+    "emphasis.strong": SyntaxHighlightStyle
+    linkUri: SyntaxHighlightStyle
+    linkText: SyntaxHighlightStyle
+}
+
+// HACK: "constructor" as a key in the syntax interface returns an error when a theme tries to use it.
+// For now hack around it by omiting constructor as a valid key for overrides.
+export type ThemeSyntax = Partial<Omit<Syntax, "constructor">>

styles/src/themes/common/ramps.ts 🔗

@@ -7,6 +7,7 @@ import {
     Style,
     Styles,
     StyleSet,
+    ThemeSyntax,
 } from "./colorScheme"
 
 export function colorRamp(color: Color): Scale {
@@ -18,7 +19,8 @@ export function colorRamp(color: Color): Scale {
 export function createColorScheme(
     name: string,
     isLight: boolean,
-    colorRamps: { [rampName: string]: Scale }
+    colorRamps: { [rampName: string]: Scale },
+    syntax?: ThemeSyntax
 ): ColorScheme {
     // Chromajs scales from 0 to 1 flipped if isLight is true
     let ramps: RampSet = {} as any
@@ -94,6 +96,7 @@ export function createColorScheme(
         modalShadow,
 
         players,
+        syntax,
     }
 }
 

styles/src/themes/one-dark.ts 🔗

@@ -1,10 +1,22 @@
 import chroma from "chroma-js"
-import { Meta } from "./common/colorScheme"
+import { Meta, ThemeSyntax } from "./common/colorScheme"
 import { colorRamp, createColorScheme } from "./common/ramps"
 
 const name = "One Dark"
 
-export const dark = createColorScheme(`${name}`, false, {
+const color = {
+    white: "#ACB2BE",
+    grey: "#5D636F",
+    red: "#D07277",
+    orange: "#C0966B",
+    yellow: "#DFC184",
+    green: "#A1C181",
+    teal: "#6FB4C0",
+    blue: "#74ADE9",
+    purple: "#B478CF",
+}
+
+const ramps = {
     neutral: chroma
         .scale([
             "#282c34",
@@ -17,16 +29,31 @@ export const dark = createColorScheme(`${name}`, false, {
             "#c8ccd4",
         ])
         .domain([0.05, 0.22, 0.25, 0.45, 0.62, 0.8, 0.9, 1]),
-
-    red: colorRamp(chroma("#e06c75")),
-    orange: colorRamp(chroma("#d19a66")),
-    yellow: colorRamp(chroma("#e5c07b")),
-    green: colorRamp(chroma("#98c379")),
-    cyan: colorRamp(chroma("#56b6c2")),
-    blue: colorRamp(chroma("#61afef")),
-    violet: colorRamp(chroma("#c678dd")),
+    red: colorRamp(chroma(color.red)),
+    orange: colorRamp(chroma(color.orange)),
+    yellow: colorRamp(chroma(color.yellow)),
+    green: colorRamp(chroma(color.green)),
+    cyan: colorRamp(chroma(color.teal)),
+    blue: colorRamp(chroma(color.blue)),
+    violet: colorRamp(chroma(color.purple)),
     magenta: colorRamp(chroma("#be5046")),
-})
+}
+
+const syntax: ThemeSyntax = {
+    primary: { color: color.white },
+    comment: { color: color.grey },
+    function: { color: color.blue },
+    type: { color: color.teal },
+    property: { color: color.red },
+    number: { color: color.orange },
+    string: { color: color.green },
+    keyword: { color: color.purple },
+    boolean: { color: color.orange },
+    punctuation: { color: color.white },
+    operator: { color: color.teal },
+}
+
+export const dark = createColorScheme(name, false, ramps, syntax)
 
 export const meta: Meta = {
     name,

styles/src/themes/one-light.ts 🔗

@@ -1,32 +1,70 @@
 import chroma from "chroma-js"
-import { Meta } from "./common/colorScheme"
+import { fontWeights } from "../common"
+import { Meta, ThemeSyntax } from "./common/colorScheme"
 import { colorRamp, createColorScheme } from "./common/ramps"
 
 const name = "One Light"
 
-export const light = createColorScheme(`${name}`, true, {
+const color = {
+    black: "#383A41",
+    grey: "#A2A3A7",
+    red: "#D36050",
+    orange: "#AD6F26",
+    yellow: "#DFC184",
+    green: "#659F58",
+    teal: "#3982B7",
+    blue: "#5B79E3",
+    purple: "#A449AB",
+    magenta: "#994EA6",
+}
+
+const ramps = {
     neutral: chroma
         .scale([
-            "#090a0b",
-            "#202227",
-            "#383a42",
+            "#383A41",
+            "#535456",
             "#696c77",
-            "#a0a1a7",
-            "#e5e5e6",
-            "#f0f0f1",
-            "#fafafa",
+            "#9D9D9F",
+            "#A9A9A9",
+            "#DBDBDC",
+            "#EAEAEB",
+            "#FAFAFA",
         ])
         .domain([0.05, 0.22, 0.25, 0.45, 0.62, 0.8, 0.9, 1]),
+    red: colorRamp(chroma(color.red)),
+    orange: colorRamp(chroma(color.orange)),
+    yellow: colorRamp(chroma(color.yellow)),
+    green: colorRamp(chroma(color.green)),
+    cyan: colorRamp(chroma(color.teal)),
+    blue: colorRamp(chroma(color.blue)),
+    violet: colorRamp(chroma(color.purple)),
+    magenta: colorRamp(chroma(color.magenta)),
+}
+
+const syntax: ThemeSyntax = {
+    primary: { color: color.black },
+    "variable.special": { color: color.orange },
+    comment: { color: color.grey },
+    punctuation: { color: color.black },
+    keyword: { color: color.purple },
+    function: { color: color.blue },
+    type: { color: color.teal },
+    variant: { color: color.blue },
+    property: { color: color.red },
+    enum: { color: color.red },
+    operator: { color: color.teal },
+    string: { color: color.green },
+    number: { color: color.orange },
+    boolean: { color: color.orange },
+    title: { color: color.red, weight: fontWeights.normal },
+    "emphasis.strong": {
+        color: color.orange,
+    },
+    linkText: { color: color.blue },
+    linkUri: { color: color.teal },
+}
 
-    red: colorRamp(chroma("#ca1243")),
-    orange: colorRamp(chroma("#d75f00")),
-    yellow: colorRamp(chroma("#c18401")),
-    green: colorRamp(chroma("#50a14f")),
-    cyan: colorRamp(chroma("#0184bc")),
-    blue: colorRamp(chroma("#4078f2")),
-    violet: colorRamp(chroma("#a626a4")),
-    magenta: colorRamp(chroma("#986801")),
-})
+export const light = createColorScheme(name, true, ramps, syntax)
 
 export const meta: Meta = {
     name,