Extract syntax highlighting properties from tree-sitter highlight queries

Nate Butler created

Change summary

crates/zed/src/languages/lua/highlights.scm        |   4 
crates/zed/src/languages/php/highlights.scm        |   4 
crates/zed/src/languages/typescript/highlights.scm |   6 
styles/src/style_tree/editor.ts                    |  30 
styles/src/theme/create_theme.ts                   |  11 
styles/src/theme/syntax.ts                         | 382 ++-------------
styles/src/theme/theme_config.ts                   |   4 
styles/src/theme/tokens/theme.ts                   |   6 
styles/src/themes/atelier/common.ts                |   5 
styles/src/themes/ayu/common.ts                    |   6 
styles/src/themes/gruvbox/gruvbox-common.ts        |   6 
styles/src/themes/one/one-dark.ts                  |   4 
styles/src/themes/one/one-light.ts                 |   2 
styles/src/themes/rose-pine/common.ts              |   4 
styles/src/types/extract_syntax_types.ts           | 102 ++++
styles/src/types/syntax.ts                         | 203 ++++++++
16 files changed, 419 insertions(+), 360 deletions(-)

Detailed changes

crates/zed/src/languages/php/highlights.scm 🔗

@@ -47,8 +47,8 @@
 ((name) @constant.builtin
  (#match? @constant.builtin "^__[A-Z][A-Z\d_]+__$"))
 
-((name) @constructor
- (#match? @constructor "^[A-Z]"))
+((name) @method.constructor
+(#match? @method.constructor "^[A-Z]"))
 
 ((name) @variable.builtin
  (#eq? @variable.builtin "this"))

crates/zed/src/languages/typescript/highlights.scm 🔗

@@ -43,8 +43,8 @@
 
 ; Special identifiers
 
-((identifier) @constructor
- (#match? @constructor "^[A-Z]"))
+((identifier) @method.constructor
+ (#match? @method.constructor "^[A-Z]"))
 
 ((identifier) @type
  (#match? @type "^[A-Z]"))
@@ -218,4 +218,4 @@
   "type"
   "readonly"
   "override"
-] @keyword
+] @keyword

styles/src/style_tree/editor.ts 🔗

@@ -9,9 +9,9 @@ import {
 } from "./components"
 import hover_popover from "./hover_popover"
 
-import { build_syntax } from "../theme/syntax"
 import { interactive, toggleable } from "../element"
 import { useTheme } from "../theme"
+import chroma from "chroma-js"
 
 export default function editor(): any {
     const theme = useTheme()
@@ -48,16 +48,28 @@ export default function editor(): any {
         }
     }
 
-    const syntax = build_syntax()
-
     return {
-        text_color: syntax.primary.color,
+        text_color: theme.syntax.primary.color,
         background: background(layer),
         active_line_background: with_opacity(background(layer, "on"), 0.75),
         highlighted_line_background: background(layer, "on"),
         // Inline autocomplete suggestions, Co-pilot suggestions, etc.
-        hint: syntax.hint,
-        suggestion: syntax.predictive,
+        hint: chroma
+            .mix(
+                theme.ramps.neutral(0.6).hex(),
+                theme.ramps.blue(0.4).hex(),
+                0.45,
+                "lch"
+            )
+            .hex(),
+        suggestion: chroma
+            .mix(
+                theme.ramps.neutral(0.4).hex(),
+                theme.ramps.blue(0.4).hex(),
+                0.45,
+                "lch"
+            )
+            .hex(),
         code_actions: {
             indicator: toggleable({
                 base: interactive({
@@ -255,8 +267,8 @@ export default function editor(): any {
         invalid_warning_diagnostic: diagnostic(theme.middle, "base"),
         hover_popover: hover_popover(),
         link_definition: {
-            color: syntax.link_uri.color,
-            underline: syntax.link_uri.underline,
+            color: theme.syntax.link_uri.color,
+            underline: theme.syntax.link_uri.underline,
         },
         jump_icon: interactive({
             base: {
@@ -314,6 +326,6 @@ export default function editor(): any {
                 color: border_color(layer),
             },
         },
-        syntax,
+        syntax: theme.syntax,
     }
 }

styles/src/theme/create_theme.ts 🔗

@@ -1,12 +1,12 @@
 import { Scale, Color } from "chroma-js"
-import { Syntax, ThemeSyntax, SyntaxHighlightStyle } from "./syntax"
-export { Syntax, ThemeSyntax, SyntaxHighlightStyle }
 import {
     ThemeConfig,
     ThemeAppearance,
-    ThemeConfigInputColors,
+    ThemeConfigInputColors
 } from "./theme_config"
 import { get_ramps } from "./ramps"
+import { syntaxStyle } from "./syntax"
+import { Syntax } from "../types/syntax"
 
 export interface Theme {
     name: string
@@ -31,7 +31,7 @@ export interface Theme {
     modal_shadow: Shadow
 
     players: Players
-    syntax?: Partial<ThemeSyntax>
+    syntax: Syntax
 }
 
 export interface Meta {
@@ -119,7 +119,6 @@ export function create_theme(theme: ThemeConfig): Theme {
         name,
         appearance,
         input_color,
-        override: { syntax },
     } = theme
 
     const is_light = appearance === ThemeAppearance.Light
@@ -162,6 +161,8 @@ export function create_theme(theme: ThemeConfig): Theme {
         "7": player(ramps.yellow),
     }
 
+    const syntax = syntaxStyle(ramps, theme.override.syntax ? theme.override.syntax : {})
+
     return {
         name,
         is_light,

styles/src/theme/syntax.ts 🔗

@@ -1,332 +1,80 @@
 import deepmerge from "deepmerge"
-import { FontWeight, font_weights, useTheme } from "../common"
-import chroma from "chroma-js"
+import { font_weights, ThemeConfigInputSyntax, RampSet } from "../common"
+import { Syntax, SyntaxHighlightStyle, allSyntaxKeys } from "../types/syntax"
 
-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>
+// Apply defaults to any missing syntax properties that are not defined manually
+function apply_defaults(ramps: RampSet, syntax_highlights: Partial<Syntax>): Syntax {
+    const restKeys: (keyof Syntax)[] = allSyntaxKeys.filter(key => !syntax_highlights[key])
 
-const default_syntax_highlight_style: Omit<SyntaxHighlightStyle, "color"> = {
-    weight: "normal",
-    underline: false,
-    italic: false,
-}
-
-function build_default_syntax(): Syntax {
-    const theme = useTheme()
+    const completeSyntax: Syntax = {} as Syntax
 
-    // Make a temporary object that is allowed to be missing
-    // the "color" property for each style
-    const syntax: {
-        [key: string]: Omit<SyntaxHighlightStyle, "color">
-    } = {}
+    const defaults: SyntaxHighlightStyle = {
+        color: ramps.neutral(1).hex(),
+    }
 
-    // then spread the default to each style
-    for (const key of Object.keys({} as Syntax)) {
-        syntax[key as keyof Syntax] = {
-            ...default_syntax_highlight_style,
+    for (const key of restKeys) {
+        {
+            completeSyntax[key] = {
+                ...defaults,
+            }
         }
     }
 
-    // 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 mergedBaseSyntax = Object.assign(completeSyntax, syntax_highlights)
 
-    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
+    return mergedBaseSyntax
 }
 
-export function build_syntax(): Syntax {
-    const theme = useTheme()
-
-    const default_syntax: Syntax = build_default_syntax()
+// Merge the base syntax with the theme syntax overrides
+// This is a deep merge, so any nested properties will be merged as well
+// This allows for a theme to only override a single property of a syntax highlight style
+const merge_syntax = (baseSyntax: Syntax, theme_syntax_overrides: ThemeConfigInputSyntax): Syntax => {
+    return deepmerge<Syntax, ThemeConfigInputSyntax>(baseSyntax, theme_syntax_overrides, {
+        arrayMerge: (destinationArray, sourceArray) => [
+            ...destinationArray,
+            ...sourceArray,
+        ],
+    })
+}
 
-    if (!theme.syntax) {
-        return default_syntax
+/** Returns a complete Syntax object of the combined styles of a theme's syntax overrides and the default syntax styles */
+export const syntaxStyle = (ramps: RampSet, theme_syntax_overrides: ThemeConfigInputSyntax): Syntax => {
+    const syntax_highlights: Partial<Syntax> = {
+        "comment": { color: ramps.neutral(0.71).hex() },
+        "comment.doc": { color: ramps.neutral(0.71).hex() },
+        primary: { color: ramps.neutral(1).hex() },
+        emphasis: { color: ramps.blue(0.5).hex() },
+        "emphasis.strong": { color: ramps.blue(0.5).hex(), weight: font_weights.bold },
+        link_uri: { color: ramps.green(0.5).hex(), underline: true },
+        link_text: { color: ramps.orange(0.5).hex(), italic: true },
+        "text.literal": { color: ramps.orange(0.5).hex() },
+        punctuation: { color: ramps.neutral(0.86).hex() },
+        "punctuation.bracket": { color: ramps.neutral(0.86).hex() },
+        "punctuation.special": { color: ramps.neutral(0.86).hex() },
+        "punctuation.delimiter": { color: ramps.neutral(0.86).hex() },
+        "punctuation.list_marker": { color: ramps.neutral(0.86).hex() },
+        string: { color: ramps.orange(0.5).hex() },
+        "string.special": { color: ramps.orange(0.5).hex() },
+        "string.special.symbol": { color: ramps.orange(0.5).hex() },
+        "string.escape": { color: ramps.neutral(0.71).hex() },
+        "string.regex": { color: ramps.orange(0.5).hex() },
+        "method.constructor": { color: ramps.blue(0.5).hex() },
+        type: { color: ramps.cyan(0.5).hex() },
+        variable: { color: ramps.neutral(1).hex() },
+        label: { color: ramps.blue(0.5).hex() },
+        attribute: { color: ramps.blue(0.5).hex() },
+        property: { color: ramps.blue(0.5).hex() },
+        constant: { color: ramps.green(0.5).hex() },
+        keyword: { color: ramps.blue(0.5).hex() },
+        operator: { color: ramps.orange(0.5).hex() },
+        number: { color: ramps.green(0.5).hex() },
+        boolean: { color: ramps.green(0.5).hex() },
+        function: { color: ramps.yellow(0.5).hex() },
+        preproc: { color: ramps.neutral(1).hex() },
+        embedded: { color: ramps.neutral(1).hex() },
     }
 
-    const syntax = deepmerge<Syntax, Partial<ThemeSyntax>>(
-        default_syntax,
-        theme.syntax,
-        {
-            arrayMerge: (destinationArray, sourceArray) => [
-                ...destinationArray,
-                ...sourceArray,
-            ],
-        }
-    )
-
-    return syntax
+    const baseSyntax = apply_defaults(ramps, syntax_highlights)
+    const mergedSyntax = merge_syntax(baseSyntax, theme_syntax_overrides)
+    return mergedSyntax
 }

styles/src/theme/theme_config.ts 🔗

@@ -1,5 +1,5 @@
 import { Scale, Color } from "chroma-js"
-import { Syntax } from "./syntax"
+import { SyntaxHighlightStyle, SyntaxProperty } from "../types/syntax"
 
 interface ThemeMeta {
     /** The name of the theme */
@@ -55,7 +55,7 @@ export type ThemeConfigInputColorsKeys = keyof ThemeConfigInputColors
  * }
  * ```
  */
-export type ThemeConfigInputSyntax = Partial<Syntax>
+export type ThemeConfigInputSyntax = Partial<Record<SyntaxProperty, Partial<SyntaxHighlightStyle>>>
 
 interface ThemeConfigOverrides {
     syntax: ThemeConfigInputSyntax

styles/src/theme/tokens/theme.ts 🔗

@@ -6,15 +6,13 @@ import {
 } from "@tokens-studio/types"
 import {
     Shadow,
-    SyntaxHighlightStyle,
-    ThemeSyntax,
 } from "../create_theme"
 import { LayerToken, layer_token } from "./layer"
 import { PlayersToken, players_token } from "./players"
 import { color_token } from "./token"
-import { Syntax } from "../syntax"
 import editor from "../../style_tree/editor"
 import { useTheme } from "../../../src/common"
+import { Syntax, SyntaxHighlightStyle } from "../../types/syntax"
 
 interface ThemeTokens {
     name: SingleOtherToken
@@ -51,7 +49,7 @@ const modal_shadow_token = (): SingleBoxShadowToken => {
     return create_shadow_token(shadow, "modal_shadow")
 }
 
-type ThemeSyntaxColorTokens = Record<keyof ThemeSyntax, SingleColorToken>
+type ThemeSyntaxColorTokens = Record<keyof Syntax, SingleColorToken>
 
 function syntax_highlight_style_color_tokens(
     syntax: Syntax

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

@@ -1,4 +1,4 @@
-import { ThemeLicenseType, ThemeSyntax, ThemeFamilyMeta } from "../../common"
+import { ThemeLicenseType, ThemeFamilyMeta, ThemeConfigInputSyntax } from "../../common"
 
 export interface Variant {
     colors: {
@@ -29,7 +29,7 @@ export const meta: ThemeFamilyMeta = {
         "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave/",
 }
 
-export const build_syntax = (variant: Variant): ThemeSyntax => {
+export const build_syntax = (variant: Variant): ThemeConfigInputSyntax => {
     const { colors } = variant
     return {
         primary: { color: colors.base06 },
@@ -50,7 +50,6 @@ export const build_syntax = (variant: Variant): ThemeSyntax => {
         property: { color: colors.base08 },
         variable: { color: colors.base06 },
         "variable.special": { color: colors.base0E },
-        variant: { color: colors.base0A },
         keyword: { color: colors.base0E },
     }
 }

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

@@ -3,8 +3,8 @@ import {
     chroma,
     color_ramp,
     ThemeLicenseType,
-    ThemeSyntax,
     ThemeFamilyMeta,
+    ThemeConfigInputSyntax,
 } from "../../common"
 
 export const ayu = {
@@ -27,7 +27,7 @@ export const build_theme = (t: typeof dark, light: boolean) => {
         purple: t.syntax.constant.hex(),
     }
 
-    const syntax: ThemeSyntax = {
+    const syntax: ThemeConfigInputSyntax = {
         constant: { color: t.syntax.constant.hex() },
         "string.regex": { color: t.syntax.regexp.hex() },
         string: { color: t.syntax.string.hex() },
@@ -61,7 +61,7 @@ export const build_theme = (t: typeof dark, light: boolean) => {
     }
 }
 
-export const build_syntax = (t: typeof dark): ThemeSyntax => {
+export const build_syntax = (t: typeof dark): ThemeConfigInputSyntax => {
     return {
         constant: { color: t.syntax.constant.hex() },
         "string.regex": { color: t.syntax.regexp.hex() },

styles/src/themes/gruvbox/gruvbox-common.ts 🔗

@@ -4,8 +4,8 @@ import {
     ThemeAppearance,
     ThemeLicenseType,
     ThemeConfig,
-    ThemeSyntax,
     ThemeFamilyMeta,
+    ThemeConfigInputSyntax,
 } from "../../common"
 
 const meta: ThemeFamilyMeta = {
@@ -214,7 +214,7 @@ const build_variant = (variant: Variant): ThemeConfig => {
         magenta: color_ramp(chroma(variant.colors.gray)),
     }
 
-    const syntax: ThemeSyntax = {
+    const syntax: ThemeConfigInputSyntax = {
         primary: { color: neutral[is_light ? 0 : 8] },
         "text.literal": { color: colors.blue },
         comment: { color: colors.gray },
@@ -229,7 +229,7 @@ const build_variant = (variant: Variant): ThemeConfig => {
         "string.special.symbol": { color: colors.aqua },
         "string.regex": { color: colors.orange },
         type: { color: colors.yellow },
-        enum: { color: colors.orange },
+        // enum: { color: colors.orange },
         tag: { color: colors.aqua },
         constant: { color: colors.yellow },
         keyword: { color: colors.red },

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

@@ -54,7 +54,6 @@ export const theme: ThemeConfig = {
         syntax: {
             boolean: { color: color.orange },
             comment: { color: color.grey },
-            enum: { color: color.red },
             "emphasis.strong": { color: color.orange },
             function: { color: color.blue },
             keyword: { color: color.purple },
@@ -73,8 +72,7 @@ export const theme: ThemeConfig = {
             "text.literal": { color: color.green },
             type: { color: color.teal },
             "variable.special": { color: color.orange },
-            variant: { color: color.blue },
-            constructor: { color: color.blue },
+            "method.constructor": { color: color.blue },
         },
     },
 }

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

@@ -55,7 +55,6 @@ export const theme: ThemeConfig = {
         syntax: {
             boolean: { color: color.orange },
             comment: { color: color.grey },
-            enum: { color: color.red },
             "emphasis.strong": { color: color.orange },
             function: { color: color.blue },
             keyword: { color: color.purple },
@@ -73,7 +72,6 @@ export const theme: ThemeConfig = {
             "text.literal": { color: color.green },
             type: { color: color.teal },
             "variable.special": { color: color.orange },
-            variant: { color: color.blue },
         },
     },
 }

styles/src/themes/rose-pine/common.ts 🔗

@@ -1,4 +1,4 @@
-import { ThemeSyntax } from "../../common"
+import { ThemeConfigInputSyntax } from "../../common"
 
 export const color = {
     default: {
@@ -54,7 +54,7 @@ export const color = {
     },
 }
 
-export const syntax = (c: typeof color.default): Partial<ThemeSyntax> => {
+export const syntax = (c: typeof color.default): ThemeConfigInputSyntax => {
     return {
         comment: { color: c.muted },
         operator: { color: c.pine },

styles/src/types/extract_syntax_types.ts 🔗

@@ -0,0 +1,102 @@
+import fs from 'fs'
+import path from 'path'
+import readline from 'readline'
+
+function escapeTypeName(name: string): string {
+    return `'${name.replace('@', '').toLowerCase()}'`
+}
+
+const generatedNote = `// This file is generated by extract_syntax_types.ts
+// Do not edit this file directly
+// It is generated from the highlight.scm files in the zed crate
+
+// To regenerate this file manually:
+//     'npm run extract-syntax-types' from ./styles`
+
+const defaultTextProperty = `    /** Default text color */
+    | 'primary'`
+
+const main = async () => {
+    const pathFromRoot = 'crates/zed/src/languages'
+    const directoryPath = path.join(__dirname, '../../../', pathFromRoot)
+    const stylesMap: Record<string, Set<string>> = {}
+    const propertyLanguageMap: Record<string, Set<string>> = {}
+
+    const processFile = async (filePath: string, language: string) => {
+        const fileStream = fs.createReadStream(filePath)
+        const rl = readline.createInterface({
+            input: fileStream,
+            crlfDelay: Infinity,
+        })
+
+        for await (const line of rl) {
+            const cleanedLine = line.replace(/"@[a-zA-Z0-9_.]*"/g, "")
+            const match = cleanedLine.match(/@(\w+\.*)*/g)
+            if (match) {
+                match.forEach((property) => {
+                    const formattedProperty = escapeTypeName(property)
+                    // Only add non-empty properties
+                    if (formattedProperty !== "''") {
+                        if (!propertyLanguageMap[formattedProperty]) {
+                            propertyLanguageMap[formattedProperty] = new Set()
+                        }
+                        propertyLanguageMap[formattedProperty].add(language)
+                    }
+                })
+            }
+        }
+    }
+
+    const directories = fs.readdirSync(directoryPath, { withFileTypes: true })
+        .filter(dirent => dirent.isDirectory())
+        .map(dirent => dirent.name)
+
+    for (const dir of directories) {
+        const highlightsFilePath = path.join(directoryPath, dir, 'highlights.scm')
+        if (fs.existsSync(highlightsFilePath)) {
+            await processFile(highlightsFilePath, dir)
+        }
+    }
+
+    for (const [language, properties] of Object.entries(stylesMap)) {
+        console.log(`${language}: ${Array.from(properties).join(', ')}`)
+    }
+
+    const sortedProperties = Object.entries(propertyLanguageMap).sort(([propA], [propB]) => propA.localeCompare(propB))
+
+    const outStream = fs.createWriteStream(path.join(__dirname, 'syntax.ts'))
+    let allProperties = ""
+    const syntaxKeys = []
+    for (const [property, languages] of sortedProperties) {
+        let languagesArray = Array.from(languages)
+        const moreThanSeven = languagesArray.length > 7
+        // Limit to the first 7 languages, append "..." if more than 7
+        languagesArray = languagesArray.slice(0, 7)
+        if (moreThanSeven) {
+            languagesArray.push('...')
+        }
+        const languagesString = languagesArray.join(', ')
+        const comment = `/** ${languagesString} */`
+        allProperties += `    ${comment}\n    | ${property} \n`
+        syntaxKeys.push(property)
+    }
+    outStream.write(`${generatedNote}
+
+export type SyntaxHighlightStyle = {
+    color: string,
+    fade_out?: number,
+    italic?: boolean,
+    underline?: boolean,
+    weight?: string,
+}
+
+export type Syntax = Record<SyntaxProperty, SyntaxHighlightStyle>
+export type SyntaxOverride = Partial<Syntax>
+
+export type SyntaxProperty = \n${defaultTextProperty}\n\n${allProperties}
+
+export const allSyntaxKeys: SyntaxProperty[] = [\n    ${syntaxKeys.join(',\n    ')}\n]`)
+    outStream.end()
+}
+
+main().catch(console.error)

styles/src/types/syntax.ts 🔗

@@ -0,0 +1,203 @@
+// This file is generated by extract_syntax_types.ts
+// Do not edit this file directly
+// It is generated from the highlight.scm files in the zed crate
+
+// To regenerate this file manually:
+//     'npm run extract-syntax-types' from ./styles
+
+export type SyntaxHighlightStyle = {
+    color: string,
+    fade_out?: number,
+    italic?: boolean,
+    underline?: boolean,
+    weight?: string,
+}
+
+export type Syntax = Record<SyntaxProperty, SyntaxHighlightStyle>
+export type SyntaxOverride = Partial<Syntax>
+
+export type SyntaxProperty = 
+    /** Default text color */
+    | 'primary'
+
+    /** elixir */
+    | '__attribute__' 
+    /** elixir */
+    | '__name__' 
+    /** elixir */
+    | '_sigil_name' 
+    /** css, heex, lua */
+    | 'attribute' 
+    /** javascript, lua, tsx, typescript, yaml */
+    | 'boolean' 
+    /** elixir */
+    | 'comment.doc' 
+    /** elixir */
+    | 'comment.unused' 
+    /** bash, c, cpp, css, elixir, elm, erb, ... */
+    | 'comment' 
+    /** elixir, go, javascript, lua, php, python, racket, ... */
+    | 'constant.builtin' 
+    /** bash, c, cpp, elixir, elm, glsl, heex, ... */
+    | 'constant' 
+    /** glsl */
+    | 'delimiter' 
+    /** bash, elixir, javascript, python, ruby, tsx, typescript */
+    | 'embedded' 
+    /** markdown */
+    | 'emphasis.strong' 
+    /** markdown */
+    | 'emphasis' 
+    /** go, python, racket, ruby, scheme */
+    | 'escape' 
+    /** lua */
+    | 'field' 
+    /** lua, php, python */
+    | 'function.builtin' 
+    /** elm, lua, rust */
+    | 'function.definition' 
+    /** ruby */
+    | 'function.method.builtin' 
+    /** go, javascript, php, python, ruby, rust, tsx, ... */
+    | 'function.method' 
+    /** rust */
+    | 'function.special.definition' 
+    /** c, cpp, glsl, rust */
+    | 'function.special' 
+    /** bash, c, cpp, css, elixir, elm, glsl, ... */
+    | 'function' 
+    /** elm */
+    | 'identifier' 
+    /** glsl */
+    | 'keyword.function' 
+    /** bash, c, cpp, css, elixir, elm, erb, ... */
+    | 'keyword' 
+    /** c, cpp, glsl */
+    | 'label' 
+    /** markdown */
+    | 'link_text' 
+    /** markdown */
+    | 'link_uri' 
+    /** lua, php, tsx, typescript */
+    | 'method.constructor' 
+    /** lua */
+    | 'method' 
+    /** heex */
+    | 'module' 
+    /** svelte */
+    | 'none' 
+    /** bash, c, cpp, css, elixir, glsl, go, ... */
+    | 'number' 
+    /** bash, c, cpp, css, elixir, elm, glsl, ... */
+    | 'operator' 
+    /** lua */
+    | 'parameter' 
+    /** lua */
+    | 'preproc' 
+    /** bash, c, cpp, css, glsl, go, html, ... */
+    | 'property' 
+    /** c, cpp, elixir, elm, heex, html, javascript, ... */
+    | 'punctuation.bracket' 
+    /** c, cpp, css, elixir, elm, heex, javascript, ... */
+    | 'punctuation.delimiter' 
+    /** markdown */
+    | 'punctuation.list_marker' 
+    /** elixir, javascript, python, ruby, tsx, typescript, yaml */
+    | 'punctuation.special' 
+    /** elixir */
+    | 'punctuation' 
+    /** glsl */
+    | 'storageclass' 
+    /** elixir, elm, yaml */
+    | 'string.escape' 
+    /** elixir, javascript, racket, ruby, tsx, typescript */
+    | 'string.regex' 
+    /** elixir, ruby */
+    | 'string.special.symbol' 
+    /** css, elixir, toml */
+    | 'string.special' 
+    /** bash, c, cpp, css, elixir, elm, glsl, ... */
+    | 'string' 
+    /** svelte */
+    | 'tag.delimiter' 
+    /** css, heex, php, svelte */
+    | 'tag' 
+    /** markdown */
+    | 'text.literal' 
+    /** markdown */
+    | 'title' 
+    /** javascript, php, rust, tsx, typescript */
+    | 'type.builtin' 
+    /** glsl */
+    | 'type.qualifier' 
+    /** c, cpp, css, elixir, elm, glsl, go, ... */
+    | 'type' 
+    /** glsl, php */
+    | 'variable.builtin' 
+    /** cpp, css, javascript, lua, racket, ruby, rust, ... */
+    | 'variable.special' 
+    /** c, cpp, elm, glsl, go, javascript, lua, ... */
+    | 'variable' 
+
+
+export const allSyntaxKeys: SyntaxProperty[] = [
+    '__attribute__',
+    '__name__',
+    '_sigil_name',
+    'attribute',
+    'boolean',
+    'comment.doc',
+    'comment.unused',
+    'comment',
+    'constant.builtin',
+    'constant',
+    'delimiter',
+    'embedded',
+    'emphasis.strong',
+    'emphasis',
+    'escape',
+    'field',
+    'function.builtin',
+    'function.definition',
+    'function.method.builtin',
+    'function.method',
+    'function.special.definition',
+    'function.special',
+    'function',
+    'identifier',
+    'keyword.function',
+    'keyword',
+    'label',
+    'link_text',
+    'link_uri',
+    'method.constructor',
+    'method',
+    'module',
+    'none',
+    'number',
+    'operator',
+    'parameter',
+    'preproc',
+    'property',
+    'punctuation.bracket',
+    'punctuation.delimiter',
+    'punctuation.list_marker',
+    'punctuation.special',
+    'punctuation',
+    'storageclass',
+    'string.escape',
+    'string.regex',
+    'string.special.symbol',
+    'string.special',
+    'string',
+    'tag.delimiter',
+    'tag',
+    'text.literal',
+    'title',
+    'type.builtin',
+    'type.qualifier',
+    'type',
+    'variable.builtin',
+    'variable.special',
+    'variable'
+]