// SPDX-FileCopyrightText: 2025-2026 Kat Suricata // SPDX-License-Identifier: (CC-BY-4.0+ OR Apache-2.0+) import type { ThemeRegistration } from 'shiki'; import { type ModeId, resolveRoleValue } from '#lib/palette.js'; /* * The scope to colour assignments are design decisions from the syntax spec. * The values come from the role tokens in palette.ts, so neither theme can * drift from the palette, and the two modes cannot drift from each other. */ function buildTheme(mode: ModeId): ThemeRegistration { const t = (token: string) => resolveRoleValue(token, mode); return { name: mode === 'dark' ? 'digital-rust' : 'digital-rust-safe-mode', type: mode === 'dark' ? 'dark' : 'light', colors: { 'editor.background': t('dr-surface-base'), 'editor.foreground': t('dr-text-primary'), 'editorLineNumber.foreground': t('dr-gutter-fg'), 'editorLineNumber.activeForeground': t('dr-gutter-fg-active'), 'editor.selectionBackground': t('dr-editor-selection-bg'), 'editor.inactiveSelectionBackground': t('dr-editor-selection-inactive-bg'), 'editorIndentGuide.background': t('dr-gutter-fg'), 'editorIndentGuide.activeBackground': t('dr-editor-indent-guide-active'), 'editorWhitespace.foreground': t('dr-editor-whitespace'), 'editorCursor.foreground': t('dr-cursor'), 'editor.lineHighlightBackground': t('dr-editor-line-highlight-bg'), 'editor.findMatchBackground': t('dr-editor-find-match-bg'), 'editor.findMatchBorder': t('dr-editor-find-match-border'), 'editor.findMatchHighlightBackground': t('dr-editor-find-match-highlight-bg'), 'editorBracketMatch.background': t('dr-gutter-fg'), 'editorBracketMatch.unexpectedBracketForeground': t('dr-editor-unexpected-bracket') }, tokenColors: [ // ─── Comments ─── { name: 'Comments', scope: ['comment', 'punctuation.definition.comment', 'comment.line.shebang'], settings: { foreground: t('dr-syntax-comment'), fontStyle: 'italic' } }, // ─── Keywords & Storage ─── { name: 'Keywords, Storage', scope: [ 'keyword', 'keyword.control', 'keyword.control.import', 'keyword.control.conditional', 'keyword.control.loop', 'storage.type', 'storage.modifier' ], settings: { foreground: t('dr-syntax-keyword') } }, // ─── Functions & Methods ─── { name: 'Functions', scope: ['entity.name.function', 'meta.function-call', 'support.function'], settings: { foreground: t('dr-syntax-function') } }, // ─── Classes & Types ─── { name: 'Classes, Types', scope: [ 'entity.name.type', 'entity.name.class', 'support.type', 'support.class', 'meta.type.annotation', 'entity.other.inherited-class' ], settings: { foreground: t('dr-syntax-type') } }, // ─── Strings & Text ─── { name: 'Strings', scope: [ 'string', 'punctuation.definition.string', 'string.quoted', 'string.regexp', 'constant.other.character-class.regexp', 'constant.character.escape' ], settings: { foreground: t('dr-syntax-string') } }, // ─── Numbers ─── { name: 'Numbers', scope: ['constant.numeric'], settings: { foreground: t('dr-syntax-number') } }, // ─── Constants & Special Values ─── { name: 'Constants, Booleans, Null, Special Values', scope: [ 'constant.language.boolean', 'constant.language.null', 'constant.language', 'constant.language.nan', 'constant.language.infinity', 'variable.language.this', 'variable.language.super', 'variable.language.self', 'variable.other.constant', 'constant.other.constant', 'variable.other.enummember', 'entity.name.constant' ], settings: { foreground: t('dr-syntax-constant') } }, // ─── Attributes & Decorators ─── { name: 'Attributes, Decorators', scope: [ 'meta.decorator', 'entity.name.function.decorator', 'meta.attribute', 'punctuation.definition.annotation', 'punctuation.decorator' ], settings: { foreground: t('dr-syntax-type'), fontStyle: 'italic' } }, // ─── Built-ins & Standard Library ─── { name: 'Built-ins', scope: [ 'support.function', 'support.class', 'support.type', 'support.constant', 'support.variable' ], settings: { foreground: t('dr-syntax-builtin') } }, // ─── Variables & Identifiers ─── { name: 'Variables', scope: [ 'variable', 'variable.parameter', 'variable.other', 'variable.other.readwrite' ], settings: { foreground: t('dr-text-primary') } }, // ─── Operators & Punctuation ─── { name: 'Operators, Punctuation', scope: [ 'keyword.operator', 'punctuation.separator', 'punctuation.terminator', 'punctuation.accessor', 'punctuation.definition.parameters', 'punctuation.definition.array', 'punctuation.definition.dictionary', 'punctuation.section.block', 'punctuation.definition.generic', 'punctuation.definition.typeparameters', 'keyword.operator.regex', 'meta.brace' ], settings: { foreground: t('dr-syntax-punctuation') } }, // ─── Markup Tags (HTML/XML) ─── { name: 'Markup Tags', scope: ['entity.name.tag', 'meta.tag', 'meta.tag.sgml'], settings: { foreground: t('dr-syntax-type') } }, // ─── Markup Attributes ─── { name: 'Markup Attributes', scope: ['entity.other.attribute-name'], settings: { foreground: t('dr-syntax-builtin') } }, // ─── Errors ─── // Raw Amber Alert fails AA on dark surfaces. The text variant passes. { name: 'Errors', scope: ['invalid', 'invalid.illegal', 'invalid.deprecated'], settings: { foreground: t('dr-syntax-error') } }, // ─── Configuration Languages (JSON/YAML/TOML) ─── { name: 'JSON/YAML/TOML Keys', scope: [ 'support.type.property-name.json', 'entity.name.tag.yaml', 'support.type.property-name.toml', 'keyword.other.definition' ], settings: { foreground: t('dr-syntax-type') } }, // ─── CSS ─── { name: 'CSS Properties', scope: [ 'support.type.property-name.css', 'support.type.vendored.property-name.css' ], settings: { foreground: t('dr-syntax-builtin') } }, { name: 'CSS Classes/IDs', scope: [ 'entity.other.attribute-name.class.css', 'entity.other.attribute-name.id.css' ], settings: { foreground: t('dr-syntax-type') } }, { name: 'CSS Pseudo-classes/Elements', scope: [ 'entity.other.attribute-name.pseudo-class.css', 'entity.other.attribute-name.pseudo-element.css' ], settings: { foreground: t('dr-syntax-builtin') } }, { name: 'CSS At-rules', scope: [ 'keyword.control.at-rule.css', 'keyword.control.at-rule.import.css', 'keyword.control.at-rule.media.css', 'keyword.control.at-rule.keyframes.css', 'meta.at-rule' ], settings: { foreground: t('dr-syntax-keyword') } }, { name: 'CSS Keywords (auto, inherit, etc.)', scope: ['support.constant.property-value.css'], settings: { foreground: t('dr-syntax-constant') } }, // ─── Markdown ─── { name: 'Markdown Headings', scope: [ 'markup.heading', 'entity.name.section.markdown', 'punctuation.definition.heading.markdown' ], settings: { foreground: t('dr-syntax-keyword'), fontStyle: 'bold' } }, { name: 'Markdown Emphasis (bold)', scope: ['markup.bold.markdown'], settings: { foreground: t('dr-text-primary'), fontStyle: 'bold' } }, { name: 'Markdown Emphasis (italic)', scope: ['markup.italic.markdown'], settings: { foreground: t('dr-text-primary'), fontStyle: 'italic' } }, { name: 'Markdown Code', scope: [ 'markup.inline.raw', 'markup.raw.inline', 'markup.fenced_code', 'punctuation.definition.raw.markdown' ], settings: { foreground: t('dr-syntax-string'), background: t('bad-sector-20') } }, { name: 'Markdown Blockquotes', scope: ['markup.quote'], settings: { foreground: t('dr-text-secondary'), fontStyle: 'italic' } }, { name: 'Markdown Links', scope: [ 'markup.underline.link', 'string.other.link.title.markdown', 'constant.other.reference.link.markdown' ], settings: { foreground: t('dr-text-link'), fontStyle: 'underline' } }, { name: 'Markdown Link URLs', scope: [ 'markup.underline.link.markdown', 'meta.link.reference.markdown', 'meta.link.inline.markdown' ], settings: { foreground: t('dr-syntax-builtin') } }, { name: 'Markdown List Markers', scope: [ 'punctuation.definition.list.begin.markdown', 'punctuation.definition.list_item.markdown' ], settings: { foreground: t('dr-syntax-punctuation') } }, { name: 'Markdown Horizontal Rule', scope: ['meta.separator.markdown'], settings: { foreground: t('dr-border-color') } }, // ─── Diff / Git ─── { name: 'Diff Header (hunk)', scope: ['meta.diff.header', 'meta.diff.range.unified'], settings: { foreground: t('dr-syntax-blue') } }, { name: 'Diff Commit Hashes', scope: ['constant.other.hash.diff'], settings: { foreground: t('dr-syntax-builtin') } }, { name: 'Diff File Paths / Metadata', scope: [ 'meta.diff.header.from-file', 'meta.diff.header.to-file', 'entity.name.file.diff', 'meta.diff.index' ], settings: { foreground: t('dr-syntax-function') } }, { name: 'Diff Inserted (+ marker)', scope: ['punctuation.definition.inserted.diff', 'punctuation.definition.plus.diff'], settings: { foreground: t('dr-syntax-string') } }, { name: 'Diff Deleted (- marker)', scope: ['punctuation.definition.deleted.diff', 'punctuation.definition.minus.diff'], settings: { foreground: t('dr-danger') } }, { name: 'Diff Modified (~ marker)', scope: ['punctuation.definition.changed.diff', 'punctuation.definition.tilde.diff'], settings: { foreground: t('dr-syntax-type') } }, { name: 'Diff Added Lines', scope: ['markup.inserted', 'meta.diff.line.inserted', 'diff.inserted'], settings: { foreground: t('dr-text-primary'), background: t('memory-leek-15') } }, { name: 'Diff Deleted Lines', scope: ['markup.deleted', 'meta.diff.line.deleted', 'diff.deleted'], settings: { foreground: t('dr-text-quiet'), background: t('amber-alert-15') } }, { name: 'Diff Modified Lines', scope: ['markup.changed', 'meta.diff.line.changed', 'diff.changed'], settings: { foreground: t('dr-text-primary'), background: t('dead-thread-10') } }, // ─── Shell / CLI ─── { name: 'Shell Shebang & Comments', scope: [ 'comment.line.number-sign.shell', 'comment.line.fish', 'comment.line.double-slash' ], settings: { foreground: t('dr-syntax-comment'), fontStyle: 'italic' } }, { name: 'Shell Commands', scope: ['entity.name.command.shell', 'support.function.builtin.shell'], settings: { foreground: t('dr-syntax-function') } }, { name: 'Shell Flags & Options', scope: [ 'keyword.operator.option.shell', 'entity.other.option.shell', 'string.unquoted.argument.shell' ], settings: { foreground: t('dr-syntax-keyword') } }, // ─── SQL ─── { name: 'SQL Functions', scope: ['support.function.aggregate.sql', 'support.function.sql'], settings: { foreground: t('dr-syntax-function') } }, { name: 'SQL Types', scope: ['support.type.sql', 'storage.type.sql'], settings: { foreground: t('dr-syntax-type') } }, // ─── Rust-Specific ─── { name: 'Rust Lifetimes', scope: ['entity.name.lifetime.rust', 'punctuation.definition.lifetime.rust'], settings: { foreground: t('dr-syntax-type'), fontStyle: 'italic' } }, { name: 'Rust Macros', scope: ['entity.name.function.macro.rust', 'meta.macro.rust'], settings: { foreground: t('dr-syntax-function') } }, { name: 'Rust Attributes', scope: ['meta.attribute.rust', 'punctuation.definition.attribute.rust'], settings: { foreground: t('dr-syntax-type'), fontStyle: 'italic' } }, // ─── C/C++ Preprocessor ─── { name: 'Preprocessor Directives', scope: [ 'keyword.control.import.include', 'keyword.control.preprocessor', 'meta.preprocessor', 'keyword.control.preprocessor.define', 'keyword.control.preprocessor.if', 'keyword.control.preprocessor.elif', 'keyword.control.preprocessor.else', 'keyword.control.preprocessor.endif', 'keyword.control.preprocessor.include', 'keyword.control.preprocessor.import', 'keyword.control.preprocessor.undef', 'keyword.control.preprocessor.error', 'keyword.control.preprocessor.warning', 'keyword.control.preprocessor.pragma' ], settings: { foreground: t('dr-syntax-keyword') } }, { name: 'Preprocessor Macro Names', scope: ['entity.name.function.preprocessor', 'entity.name.constant.preprocessor'], settings: { foreground: t('dr-syntax-constant') } }, { name: 'Preprocessor Include Paths', scope: [ 'string.quoted.double.include.c', 'string.quoted.other.lt-gt.include.c', 'string.include' ], settings: { foreground: t('dr-syntax-string') } }, // ─── String Interpolation Delimiters ─── { name: 'Interpolation Delimiters', scope: [ 'punctuation.definition.template-expression', 'punctuation.section.embedded', 'meta.template.expression' ], settings: { foreground: t('dr-syntax-punctuation') } }, // ─── Haskell / ML / Functional ─── { name: 'Type Constructors (Haskell/ML)', scope: ['constant.other.capitalized-type.haskell', 'entity.name.type.data.haskell'], settings: { foreground: t('dr-syntax-type') } }, { name: 'Data Constructors (Haskell/ML)', scope: [ 'constant.other.capitalized-constructor.haskell', 'entity.name.function.constructor.haskell' ], settings: { foreground: t('dr-syntax-constant') } }, { name: 'Type Variables (Haskell/ML)', scope: ['variable.other.generic-type.haskell', 'variable.other.type.haskell'], settings: { foreground: t('dr-syntax-type'), fontStyle: 'italic' } }, { name: 'Functional Arrows (::, =>, ->)', scope: [ 'keyword.other.double-colon.haskell', 'keyword.other.fat-arrow.haskell', 'keyword.other.arrow.haskell', 'keyword.operator.type-annotation', 'keyword.operator.arrow' ], settings: { foreground: t('dr-syntax-punctuation') } } ] }; } export const digitalRustTheme = buildTheme('dark'); export const digitalRustLightTheme = buildTheme('safe-mode');