diff --git a/apps/desktop/package-lock.json b/apps/desktop/package-lock.json index 0fa98885..70a5b54e 100644 --- a/apps/desktop/package-lock.json +++ b/apps/desktop/package-lock.json @@ -26,7 +26,6 @@ "@codemirror/merge": "^6.12.1", "@codemirror/search": "^6.7.0", "@codemirror/state": "^6.6.0", - "@codemirror/theme-one-dark": "^6.1.3", "@codemirror/view": "^6.41.1", "@excalidraw/excalidraw": "^0.18.1", "@iconify-json/catppuccin": "^1.2.17", @@ -659,18 +658,6 @@ "@marijn/find-cluster-break": "^1.0.0" } }, - "node_modules/@codemirror/theme-one-dark": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", - "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", - "license": "MIT", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/highlight": "^1.0.0" - } - }, "node_modules/@codemirror/view": { "version": "6.41.1", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.41.1.tgz", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 4ca75e5d..f62e5082 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -50,7 +50,6 @@ "@codemirror/merge": "^6.12.1", "@codemirror/search": "^6.7.0", "@codemirror/state": "^6.6.0", - "@codemirror/theme-one-dark": "^6.1.3", "@codemirror/view": "^6.41.1", "@excalidraw/excalidraw": "^0.18.1", "@iconify-json/catppuccin": "^1.2.17", diff --git a/apps/desktop/src/app/themes/amber.ts b/apps/desktop/src/app/themes/amber.ts index 79faeba7..c93e94dd 100644 --- a/apps/desktop/src/app/themes/amber.ts +++ b/apps/desktop/src/app/themes/amber.ts @@ -15,6 +15,20 @@ export const amberTheme: ThemePalette = { accent: "#d97706", iconMuted: "#8a7a60", shadowSoft: "0 18px 48px rgba(28, 20, 8, 0.07)", + codeAnchors: { + comment: "#8a7a60", + constant: "#9a3412", + escape: "#be185d", + function: "#b45309", + keyword: "#7e22ce", + markup: "#9f1239", + parameter: "#854d0e", + property: "#92400e", + string: "#166534", + type: "#78350f", + typeParameter: "#1e40af", + variable: "#1c1408", + }, }, dark: { bgPrimary: "#1a1710", @@ -29,5 +43,19 @@ export const amberTheme: ThemePalette = { accent: "#f59e0b", iconMuted: "#9e9278", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.30)", + codeAnchors: { + comment: "#a89e8a", + constant: "#fb923c", + escape: "#f9a8d4", + function: "#fcd34d", + keyword: "#c4b5fd", + markup: "#fecaca", + parameter: "#fed7aa", + property: "#fde68a", + string: "#bef264", + type: "#fdba74", + typeParameter: "#86efac", + variable: "#eae4d8", + }, }, }; diff --git a/apps/desktop/src/app/themes/ayu.ts b/apps/desktop/src/app/themes/ayu.ts index 1bdc4506..c1dcab8c 100644 --- a/apps/desktop/src/app/themes/ayu.ts +++ b/apps/desktop/src/app/themes/ayu.ts @@ -15,6 +15,20 @@ export const ayuTheme: ThemePalette = { accent: "#f29718", iconMuted: "#828e9f", shadowSoft: "0 18px 48px rgba(92, 97, 102, 0.08)", + codeAnchors: { + comment: "#adaeb1", + constant: "#a37acc", + escape: "#4cbf99", + function: "#eba400", + keyword: "#fa8532", + markup: "#86b300", + parameter: "#a37acc", + property: "#f07171", + string: "#86b300", + type: "#22a4e6", + typeParameter: "#55b4d4", + variable: "#5c6166", + }, }, dark: { bgPrimary: "#10141c", @@ -29,5 +43,19 @@ export const ayuTheme: ThemePalette = { accent: "#e6b450", iconMuted: "#8a97ad", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.35)", + codeAnchors: { + comment: "#5a6673", + constant: "#d2a6ff", + escape: "#95e6cb", + function: "#ffb454", + keyword: "#ff8f40", + markup: "#aad94c", + parameter: "#d2a6ff", + property: "#f07178", + string: "#aad94c", + type: "#59c2ff", + typeParameter: "#59c2ff", + variable: "#bfbdb6", + }, }, }; diff --git a/apps/desktop/src/app/themes/catppuccin.ts b/apps/desktop/src/app/themes/catppuccin.ts index 00ca2e56..83a20ec1 100644 --- a/apps/desktop/src/app/themes/catppuccin.ts +++ b/apps/desktop/src/app/themes/catppuccin.ts @@ -15,6 +15,20 @@ export const catppuccinTheme: ThemePalette = { accent: "#8839ef", iconMuted: "#6c6f85", shadowSoft: "0 18px 48px rgba(76, 79, 105, 0.08)", + codeAnchors: { + comment: "#7c7f93", + constant: "#fe640b", + escape: "#ea76cb", + function: "#1e66f5", + keyword: "#8839ef", + markup: "#1e66f5", + parameter: "#e64553", + property: "#179299", + string: "#40a02b", + type: "#df8e1d", + typeParameter: "#04a5e5", + variable: "#4c4f69", + }, }, dark: { bgPrimary: "#1e1e2e", @@ -29,5 +43,19 @@ export const catppuccinTheme: ThemePalette = { accent: "#cba6f7", iconMuted: "#9399b2", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.35)", + codeAnchors: { + comment: "#9399b2", + constant: "#fab387", + escape: "#f5c2e7", + function: "#89b4fa", + keyword: "#cba6f7", + markup: "#89b4fa", + parameter: "#eba0ac", + property: "#94e2d5", + string: "#a6e3a1", + type: "#f9e2af", + typeParameter: "#89dceb", + variable: "#cdd6f4", + }, }, }; diff --git a/apps/desktop/src/app/themes/claude.ts b/apps/desktop/src/app/themes/claude.ts index ac0632b5..50804eef 100644 --- a/apps/desktop/src/app/themes/claude.ts +++ b/apps/desktop/src/app/themes/claude.ts @@ -15,6 +15,20 @@ export const claudeTheme: ThemePalette = { accent: "#c15f3c", iconMuted: "#8a8780", shadowSoft: "0 18px 48px rgba(20, 20, 19, 0.07)", + codeAnchors: { + comment: "#8a8780", + constant: "#9a3412", + escape: "#be185d", + function: "#c2410c", + keyword: "#991b1b", + markup: "#7c2d12", + parameter: "#b45309", + property: "#be123c", + string: "#166534", + type: "#854d0e", + typeParameter: "#0e7490", + variable: "#141413", + }, }, dark: { bgPrimary: "#1a1917", @@ -29,5 +43,19 @@ export const claudeTheme: ThemePalette = { accent: "#d97757", iconMuted: "#908d86", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.32)", + codeAnchors: { + comment: "#a09d95", + constant: "#f97316", + escape: "#ec4899", + function: "#fb923c", + keyword: "#fca5a5", + markup: "#fcd34d", + parameter: "#fed7aa", + property: "#fbcfe8", + string: "#bef264", + type: "#facc15", + typeParameter: "#67e8f9", + variable: "#f4f3ee", + }, }, }; diff --git a/apps/desktop/src/app/themes/codex.ts b/apps/desktop/src/app/themes/codex.ts index 203ad26d..cec082d6 100644 --- a/apps/desktop/src/app/themes/codex.ts +++ b/apps/desktop/src/app/themes/codex.ts @@ -15,6 +15,20 @@ export const codexTheme: ThemePalette = { accent: "#10a37f", iconMuted: "#6e6e80", shadowSoft: "0 18px 48px rgba(0, 0, 0, 0.06)", + codeAnchors: { + comment: "#6e6e80", + constant: "#9a3412", + escape: "#be185d", + function: "#047857", + keyword: "#065f46", + markup: "#166534", + parameter: "#15803d", + property: "#0e7490", + string: "#854d0e", + type: "#0f766e", + typeParameter: "#1d4ed8", + variable: "#1a1a1a", + }, }, dark: { bgPrimary: "#1e1e20", @@ -29,5 +43,19 @@ export const codexTheme: ThemePalette = { accent: "#10a37f", iconMuted: "#8e8ea0", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.35)", + codeAnchors: { + comment: "#8e8ea0", + constant: "#fba17e", + escape: "#f472b6", + function: "#6ee7b7", + keyword: "#86efac", + markup: "#a7f3d0", + parameter: "#bbf7d0", + property: "#67e8f9", + string: "#fde68a", + type: "#5eead4", + typeParameter: "#93c5fd", + variable: "#ececf1", + }, }, }; diff --git a/apps/desktop/src/app/themes/default.ts b/apps/desktop/src/app/themes/default.ts index 5c093d7e..fabcc63d 100644 --- a/apps/desktop/src/app/themes/default.ts +++ b/apps/desktop/src/app/themes/default.ts @@ -15,6 +15,20 @@ export const defaultTheme: ThemePalette = { accent: "#6366f1", iconMuted: "#737373", shadowSoft: "0 18px 48px rgba(15, 23, 42, 0.08)", + codeAnchors: { + comment: "#737373", + constant: "#9a3412", + escape: "#be185d", + function: "#1d4ed8", + keyword: "#6d28d9", + markup: "#b42318", + parameter: "#b45309", + property: "#1e3a8a", + string: "#0b6b3a", + type: "#8a5a00", + typeParameter: "#15803d", + variable: "#1f2937", + }, }, dark: { bgPrimary: "#1c1c1c", @@ -29,5 +43,19 @@ export const defaultTheme: ThemePalette = { accent: "#818cf8", iconMuted: "#9a9a9a", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.28)", + codeAnchors: { + comment: "#8a8a8a", + constant: "#fdba74", + escape: "#f472b6", + function: "#93c5fd", + keyword: "#c4b5fd", + markup: "#fca5a5", + parameter: "#fed7aa", + property: "#fda4af", + string: "#a7f3d0", + type: "#fde68a", + typeParameter: "#86efac", + variable: "#e5e7eb", + }, }, }; diff --git a/apps/desktop/src/app/themes/everforest.ts b/apps/desktop/src/app/themes/everforest.ts index 3cf45133..09170b3d 100644 --- a/apps/desktop/src/app/themes/everforest.ts +++ b/apps/desktop/src/app/themes/everforest.ts @@ -15,6 +15,20 @@ export const everforestTheme: ThemePalette = { accent: "#8da101", iconMuted: "#829181", shadowSoft: "0 18px 48px rgba(92, 106, 114, 0.08)", + codeAnchors: { + comment: "#939f91", + constant: "#35a77c", + escape: "#f57d26", + function: "#8da101", + keyword: "#f85552", + markup: "#f57d26", + parameter: "#5c6a72", + property: "#3a94c5", + string: "#8da101", + type: "#dfa000", + typeParameter: "#dfa000", + variable: "#5c6a72", + }, }, dark: { bgPrimary: "#2D353B", @@ -29,5 +43,19 @@ export const everforestTheme: ThemePalette = { accent: "#A7C080", iconMuted: "#9AA79C", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.30)", + codeAnchors: { + comment: "#859289", + constant: "#83c092", + escape: "#e69875", + function: "#a7c080", + keyword: "#e67e80", + markup: "#e69875", + parameter: "#d3c6aa", + property: "#7fbbb3", + string: "#a7c080", + type: "#dbbc7f", + typeParameter: "#dbbc7f", + variable: "#d3c6aa", + }, }, }; diff --git a/apps/desktop/src/app/themes/forest.ts b/apps/desktop/src/app/themes/forest.ts index d7f2ab9e..8ed299f2 100644 --- a/apps/desktop/src/app/themes/forest.ts +++ b/apps/desktop/src/app/themes/forest.ts @@ -15,6 +15,20 @@ export const forestTheme: ThemePalette = { accent: "#10b981", iconMuted: "#78716c", shadowSoft: "0 18px 48px rgba(28, 25, 23, 0.06)", + codeAnchors: { + comment: "#78716c", + constant: "#b45309", + escape: "#be185d", + function: "#047857", + keyword: "#7e22ce", + markup: "#854d0e", + parameter: "#166534", + property: "#15803d", + string: "#166534", + type: "#854d0e", + typeParameter: "#0369a1", + variable: "#1c1917", + }, }, dark: { bgPrimary: "#1a1a18", @@ -29,5 +43,19 @@ export const forestTheme: ThemePalette = { accent: "#34d399", iconMuted: "#958e88", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.3)", + codeAnchors: { + comment: "#a8a29e", + constant: "#fdba74", + escape: "#f9a8d4", + function: "#6ee7b7", + keyword: "#c4b5fd", + markup: "#fcd34d", + parameter: "#d6f0a2", + property: "#a7f3d0", + string: "#bef264", + type: "#fde68a", + typeParameter: "#67e8f9", + variable: "#e7e5e4", + }, }, }; diff --git a/apps/desktop/src/app/themes/gruvbox.ts b/apps/desktop/src/app/themes/gruvbox.ts index c083460a..2d7a33be 100644 --- a/apps/desktop/src/app/themes/gruvbox.ts +++ b/apps/desktop/src/app/themes/gruvbox.ts @@ -15,6 +15,20 @@ export const gruvboxTheme: ThemePalette = { accent: "#427b58", iconMuted: "#7c6f64", shadowSoft: "0 18px 48px rgba(60, 56, 54, 0.07)", + codeAnchors: { + comment: "#928374", + constant: "#8f3f71", + escape: "#af3a03", + function: "#79740e", + keyword: "#9d0006", + markup: "#79740e", + parameter: "#076678", + property: "#427b58", + string: "#79740e", + type: "#b57614", + typeParameter: "#b57614", + variable: "#3c3836", + }, }, dark: { bgPrimary: "#282828", @@ -29,5 +43,19 @@ export const gruvboxTheme: ThemePalette = { accent: "#8ec07c", iconMuted: "#a89984", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.35)", + codeAnchors: { + comment: "#928374", + constant: "#d3869b", + escape: "#fe8019", + function: "#b8bb26", + keyword: "#fb4934", + markup: "#b8bb26", + parameter: "#83a598", + property: "#8ec07c", + string: "#b8bb26", + type: "#fabd2f", + typeParameter: "#fabd2f", + variable: "#ebdbb2", + }, }, }; diff --git a/apps/desktop/src/app/themes/index.test.ts b/apps/desktop/src/app/themes/index.test.ts new file mode 100644 index 00000000..3e5915d8 --- /dev/null +++ b/apps/desktop/src/app/themes/index.test.ts @@ -0,0 +1,67 @@ +import { afterEach, describe, expect, it } from "vitest"; + +import { + applyThemeColors, + themes, + type CodeColorAnchors, + type ThemeName, +} from "./index"; + +const CODE_CSS_VAR_MAP = { + comment: "--code-comment", + constant: "--code-constant", + escape: "--code-escape", + function: "--code-function", + keyword: "--code-keyword", + markup: "--code-markup", + parameter: "--code-parameter", + property: "--code-property", + string: "--code-string", + type: "--code-type", + typeParameter: "--code-type-parameter", + variable: "--code-variable", +} satisfies Record; + +const CODE_CSS_VAR_ENTRIES = Object.entries(CODE_CSS_VAR_MAP) as Array< + [keyof CodeColorAnchors, string] +>; + +function expectCodeVars(themeName: ThemeName, isDark: boolean) { + applyThemeColors(themeName, isDark); + + const mode = isDark ? "dark" : "light"; + const anchors = themes[themeName][mode].codeAnchors; + + for (const [key, cssVar] of CODE_CSS_VAR_ENTRIES) { + expect(document.documentElement.style.getPropertyValue(cssVar)).toBe( + anchors[key], + ); + } +} + +describe("applyThemeColors", () => { + afterEach(() => { + document.documentElement.removeAttribute("style"); + }); + + it("publishes all per-theme syntax token vars for light and dark modes", () => { + expectCodeVars("default", false); + expectCodeVars("default", true); + }); + + it("updates syntax token vars when only the theme name changes", () => { + applyThemeColors("gruvbox", false); + expect( + document.documentElement.style.getPropertyValue("--code-keyword"), + ).toBe(themes.gruvbox.light.codeAnchors.keyword); + + applyThemeColors("tokyoNight", false); + + expect( + document.documentElement.style.getPropertyValue("--code-keyword"), + ).toBe(themes.tokyoNight.light.codeAnchors.keyword); + expect(themes.tokyoNight.light.codeAnchors.keyword).not.toBe( + themes.gruvbox.light.codeAnchors.keyword, + ); + }); +}); diff --git a/apps/desktop/src/app/themes/index.ts b/apps/desktop/src/app/themes/index.ts index 4f530dc1..beca78dd 100644 --- a/apps/desktop/src/app/themes/index.ts +++ b/apps/desktop/src/app/themes/index.ts @@ -20,7 +20,26 @@ import { synthwave84Theme } from "./synthwave84"; import { claudeTheme } from "./claude"; import { codexTheme } from "./codex"; -export interface ThemeColors { +// 12 syntax-highlighting anchor colors that drive per-theme code and +// markdown coloring across CodeMirror and the static highlighter. Each +// theme curates these once per mode (light/dark) and the runtime publishes +// them as `--code-*` CSS vars on `:root`. +export interface CodeColorAnchors { + comment: string; + constant: string; + escape: string; + function: string; + keyword: string; + markup: string; + parameter: string; + property: string; + string: string; + type: string; + typeParameter: string; + variable: string; +} + +export interface ThemeUiColors { bgPrimary: string; bgSecondary: string; bgTertiary: string; @@ -35,6 +54,10 @@ export interface ThemeColors { shadowSoft: string; } +export interface ThemeColors extends ThemeUiColors { + codeAnchors: CodeColorAnchors; +} + export interface ThemePalette { label: string; light: ThemeColors; @@ -88,7 +111,7 @@ export const themes: Record = { codex: codexTheme, }; -const CSS_VAR_MAP: Record = { +const CSS_VAR_MAP: Record = { bgPrimary: "--bg-primary", bgSecondary: "--bg-secondary", bgTertiary: "--bg-tertiary", @@ -103,6 +126,21 @@ const CSS_VAR_MAP: Record = { shadowSoft: "--shadow-soft", }; +const CODE_CSS_VAR_MAP: Record = { + comment: "--code-comment", + constant: "--code-constant", + escape: "--code-escape", + function: "--code-function", + keyword: "--code-keyword", + markup: "--code-markup", + parameter: "--code-parameter", + property: "--code-property", + string: "--code-string", + type: "--code-type", + typeParameter: "--code-type-parameter", + variable: "--code-variable", +}; + export function applyThemeColors(name: ThemeName, isDark: boolean) { if (typeof document === "undefined") return; const palette = themes[name]; @@ -110,6 +148,13 @@ export function applyThemeColors(name: ThemeName, isDark: boolean) { const el = document.documentElement; for (const [key, cssVar] of Object.entries(CSS_VAR_MAP)) { - el.style.setProperty(cssVar, colors[key as keyof ThemeColors]); + el.style.setProperty(cssVar, colors[key as keyof ThemeUiColors]); + } + + for (const [key, cssVar] of Object.entries(CODE_CSS_VAR_MAP)) { + el.style.setProperty( + cssVar, + colors.codeAnchors[key as keyof CodeColorAnchors], + ); } } diff --git a/apps/desktop/src/app/themes/kanagawa.ts b/apps/desktop/src/app/themes/kanagawa.ts index 24e78e11..9221980c 100644 --- a/apps/desktop/src/app/themes/kanagawa.ts +++ b/apps/desktop/src/app/themes/kanagawa.ts @@ -15,6 +15,20 @@ export const kanagawaTheme: ThemePalette = { accent: "#4d699b", iconMuted: "#8a8980", shadowSoft: "0 18px 48px rgba(84, 84, 100, 0.10)", + codeAnchors: { + comment: "#8a8980", + constant: "#cc6d00", + escape: "#4e8ca2", + function: "#4d699b", + keyword: "#624c83", + markup: "#cc6d00", + parameter: "#5d57a3", + property: "#b35b79", + string: "#6f894e", + type: "#597b75", + typeParameter: "#597b75", + variable: "#545464", + }, }, dark: { bgPrimary: "#1F1F28", @@ -29,5 +43,19 @@ export const kanagawaTheme: ThemePalette = { accent: "#7E9CD8", iconMuted: "#9A998F", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.35)", + codeAnchors: { + comment: "#727169", + constant: "#ffa066", + escape: "#9cabca", + function: "#7e9cd8", + keyword: "#957fb8", + markup: "#ffa066", + parameter: "#b8b4d0", + property: "#7fb4ca", + string: "#98bb6c", + type: "#7aa89f", + typeParameter: "#7aa89f", + variable: "#dcd7ba", + }, }, }; diff --git a/apps/desktop/src/app/themes/lavender.ts b/apps/desktop/src/app/themes/lavender.ts index aa309208..fe44650b 100644 --- a/apps/desktop/src/app/themes/lavender.ts +++ b/apps/desktop/src/app/themes/lavender.ts @@ -15,6 +15,20 @@ export const lavenderTheme: ThemePalette = { accent: "#8b5cf6", iconMuted: "#7c6f96", shadowSoft: "0 18px 48px rgba(26, 21, 37, 0.07)", + codeAnchors: { + comment: "#7c6f96", + constant: "#9a3412", + escape: "#be185d", + function: "#6d28d9", + keyword: "#5b21b6", + markup: "#7e22ce", + parameter: "#7c2d12", + property: "#5b21b6", + string: "#047857", + type: "#854d0e", + typeParameter: "#0e7490", + variable: "#1a1525", + }, }, dark: { bgPrimary: "#18141f", @@ -29,5 +43,19 @@ export const lavenderTheme: ThemePalette = { accent: "#a78bfa", iconMuted: "#8d82a3", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.32)", + codeAnchors: { + comment: "#9b90ad", + constant: "#fca5a5", + escape: "#f472b6", + function: "#c4b5fd", + keyword: "#a78bfa", + markup: "#ddd6fe", + parameter: "#fde68a", + property: "#f9a8d4", + string: "#a7f3d0", + type: "#fcd34d", + typeParameter: "#67e8f9", + variable: "#e8e2f0", + }, }, }; diff --git a/apps/desktop/src/app/themes/nightOwl.ts b/apps/desktop/src/app/themes/nightOwl.ts index 28435486..2d152520 100644 --- a/apps/desktop/src/app/themes/nightOwl.ts +++ b/apps/desktop/src/app/themes/nightOwl.ts @@ -15,6 +15,20 @@ export const nightOwlTheme: ThemePalette = { accent: "#2aa298", iconMuted: "#7e8a9e", shadowSoft: "0 18px 48px rgba(64, 63, 83, 0.08)", + codeAnchors: { + comment: "#989fb1", + constant: "#4876d6", + escape: "#aa0982", + function: "#4876d6", + keyword: "#994cc3", + markup: "#4876d6", + parameter: "#0c969b", + property: "#0c969b", + string: "#4876d6", + type: "#4876d6", + typeParameter: "#4876d6", + variable: "#403f53", + }, }, dark: { bgPrimary: "#011627", @@ -29,5 +43,19 @@ export const nightOwlTheme: ThemePalette = { accent: "#82aaff", iconMuted: "#8aa0ba", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.40)", + codeAnchors: { + comment: "#637777", + constant: "#82aaff", + escape: "#f78c6c", + function: "#82aaff", + keyword: "#c792ea", + markup: "#82b1ff", + parameter: "#7fdbca", + property: "#7fdbca", + string: "#ecc48d", + type: "#c5e478", + typeParameter: "#5f7e97", + variable: "#d6deeb", + }, }, }; diff --git a/apps/desktop/src/app/themes/nord.ts b/apps/desktop/src/app/themes/nord.ts index d58b4ea8..933dafd1 100644 --- a/apps/desktop/src/app/themes/nord.ts +++ b/apps/desktop/src/app/themes/nord.ts @@ -15,6 +15,20 @@ export const nordTheme: ThemePalette = { accent: "#5e81ac", iconMuted: "#4c566a", shadowSoft: "0 18px 48px rgba(46, 52, 64, 0.08)", + codeAnchors: { + comment: "#4c566a", + constant: "#b48ead", + escape: "#d08770", + function: "#5e81ac", + keyword: "#5e81ac", + markup: "#5e81ac", + parameter: "#2e3440", + property: "#2e3440", + string: "#587539", + type: "#5e81ac", + typeParameter: "#5e81ac", + variable: "#2e3440", + }, }, dark: { bgPrimary: "#2e3440", @@ -29,5 +43,19 @@ export const nordTheme: ThemePalette = { accent: "#88c0d0", iconMuted: "#b0b8c8", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.30)", + codeAnchors: { + comment: "#616e88", + constant: "#b48ead", + escape: "#ebcb8b", + function: "#88c0d0", + keyword: "#81a1c1", + markup: "#88c0d0", + parameter: "#d8dee9", + property: "#d8dee9", + string: "#a3be8c", + type: "#8fbcbb", + typeParameter: "#8fbcbb", + variable: "#d8dee9", + }, }, }; diff --git a/apps/desktop/src/app/themes/ocean.ts b/apps/desktop/src/app/themes/ocean.ts index 62e14426..57ba7bec 100644 --- a/apps/desktop/src/app/themes/ocean.ts +++ b/apps/desktop/src/app/themes/ocean.ts @@ -15,6 +15,20 @@ export const oceanTheme: ThemePalette = { accent: "#0ea5e9", iconMuted: "#64748b", shadowSoft: "0 18px 48px rgba(15, 23, 42, 0.07)", + codeAnchors: { + comment: "#64748b", + constant: "#b45309", + escape: "#be185d", + function: "#0284c7", + keyword: "#1d4ed8", + markup: "#0369a1", + parameter: "#0e7490", + property: "#0f766e", + string: "#047857", + type: "#065f46", + typeParameter: "#155e75", + variable: "#0f172a", + }, }, dark: { bgPrimary: "#0f172a", @@ -29,5 +43,19 @@ export const oceanTheme: ThemePalette = { accent: "#38bdf8", iconMuted: "#8494a7", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.35)", + codeAnchors: { + comment: "#64748b", + constant: "#fba17e", + escape: "#f9a8d4", + function: "#38bdf8", + keyword: "#93c5fd", + markup: "#7dd3fc", + parameter: "#bae6fd", + property: "#5eead4", + string: "#86efac", + type: "#a5f3fc", + typeParameter: "#67e8f9", + variable: "#e2e8f0", + }, }, }; diff --git a/apps/desktop/src/app/themes/rose.ts b/apps/desktop/src/app/themes/rose.ts index 6cf5ac2a..e74ab09a 100644 --- a/apps/desktop/src/app/themes/rose.ts +++ b/apps/desktop/src/app/themes/rose.ts @@ -15,6 +15,20 @@ export const roseTheme: ThemePalette = { accent: "#e11d48", iconMuted: "#886f78", shadowSoft: "0 18px 48px rgba(31, 18, 21, 0.06)", + codeAnchors: { + comment: "#886f78", + constant: "#9f1239", + escape: "#be185d", + function: "#9f1239", + keyword: "#be123c", + markup: "#9f1239", + parameter: "#9d174d", + property: "#831843", + string: "#065f46", + type: "#854d0e", + typeParameter: "#0369a1", + variable: "#1f1215", + }, }, dark: { bgPrimary: "#1a1215", @@ -29,5 +43,19 @@ export const roseTheme: ThemePalette = { accent: "#fb7185", iconMuted: "#9e8a91", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.32)", + codeAnchors: { + comment: "#a8949b", + constant: "#fca5a5", + escape: "#f9a8d4", + function: "#fda4af", + keyword: "#f472b6", + markup: "#fecaca", + parameter: "#fed7aa", + property: "#fbcfe8", + string: "#bef264", + type: "#fcd34d", + typeParameter: "#86efac", + variable: "#ede4e7", + }, }, }; diff --git a/apps/desktop/src/app/themes/rosePine.ts b/apps/desktop/src/app/themes/rosePine.ts index 2d5b2176..f126d0ab 100644 --- a/apps/desktop/src/app/themes/rosePine.ts +++ b/apps/desktop/src/app/themes/rosePine.ts @@ -15,6 +15,20 @@ export const rosePineTheme: ThemePalette = { accent: "#d7827e", iconMuted: "#9893a5", shadowSoft: "0 18px 48px rgba(87, 82, 121, 0.08)", + codeAnchors: { + comment: "#9893a5", + constant: "#286983", + escape: "#286983", + function: "#d7827e", + keyword: "#286983", + markup: "#56949f", + parameter: "#907aa9", + property: "#56949f", + string: "#ea9d34", + type: "#56949f", + typeParameter: "#56949f", + variable: "#575279", + }, }, dark: { bgPrimary: "#191724", @@ -29,5 +43,19 @@ export const rosePineTheme: ThemePalette = { accent: "#ebbcba", iconMuted: "#7e79a0", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.35)", + codeAnchors: { + comment: "#6e6a86", + constant: "#31748f", + escape: "#31748f", + function: "#ebbcba", + keyword: "#31748f", + markup: "#9ccfd8", + parameter: "#c4a7e7", + property: "#9ccfd8", + string: "#f6c177", + type: "#9ccfd8", + typeParameter: "#9ccfd8", + variable: "#e0def4", + }, }, }; diff --git a/apps/desktop/src/app/themes/solarized.ts b/apps/desktop/src/app/themes/solarized.ts index 70ae0a26..0a8a7cff 100644 --- a/apps/desktop/src/app/themes/solarized.ts +++ b/apps/desktop/src/app/themes/solarized.ts @@ -15,6 +15,20 @@ export const solarizedTheme: ThemePalette = { accent: "#268bd2", iconMuted: "#586e75", shadowSoft: "0 18px 48px rgba(7, 54, 66, 0.07)", + codeAnchors: { + comment: "#93a1a1", + constant: "#cb4b16", + escape: "#cb4b16", + function: "#268bd2", + keyword: "#859900", + markup: "#268bd2", + parameter: "#657b83", + property: "#657b83", + string: "#2aa198", + type: "#cb4b16", + typeParameter: "#cb4b16", + variable: "#268bd2", + }, }, dark: { bgPrimary: "#002b36", @@ -29,5 +43,19 @@ export const solarizedTheme: ThemePalette = { accent: "#2aa198", iconMuted: "#839496", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.40)", + codeAnchors: { + comment: "#586e75", + constant: "#cb4b16", + escape: "#cb4b16", + function: "#268bd2", + keyword: "#859900", + markup: "#268bd2", + parameter: "#839496", + property: "#839496", + string: "#2aa198", + type: "#cb4b16", + typeParameter: "#cb4b16", + variable: "#268bd2", + }, }, }; diff --git a/apps/desktop/src/app/themes/sunset.ts b/apps/desktop/src/app/themes/sunset.ts index 2b9e7b93..a3ea9991 100644 --- a/apps/desktop/src/app/themes/sunset.ts +++ b/apps/desktop/src/app/themes/sunset.ts @@ -15,6 +15,20 @@ export const sunsetTheme: ThemePalette = { accent: "#ea580c", iconMuted: "#8c6e5a", shadowSoft: "0 18px 48px rgba(33, 21, 14, 0.07)", + codeAnchors: { + comment: "#8c6e5a", + constant: "#b45309", + escape: "#be123c", + function: "#c2410c", + keyword: "#7c2d12", + markup: "#9f1239", + parameter: "#78350f", + property: "#854d0e", + string: "#166534", + type: "#4a044e", + typeParameter: "#1e40af", + variable: "#21150e", + }, }, dark: { bgPrimary: "#1a1410", @@ -29,5 +43,19 @@ export const sunsetTheme: ThemePalette = { accent: "#fb923c", iconMuted: "#9e8774", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.32)", + codeAnchors: { + comment: "#a8917e", + constant: "#fcd34d", + escape: "#f472b6", + function: "#fdba74", + keyword: "#fb923c", + markup: "#fca5a5", + parameter: "#fed7aa", + property: "#fbcfe8", + string: "#bef264", + type: "#facc15", + typeParameter: "#67e8f9", + variable: "#ece2d8", + }, }, }; diff --git a/apps/desktop/src/app/themes/synthwave84.ts b/apps/desktop/src/app/themes/synthwave84.ts index bd1d7e7d..4ffb1279 100644 --- a/apps/desktop/src/app/themes/synthwave84.ts +++ b/apps/desktop/src/app/themes/synthwave84.ts @@ -15,6 +15,20 @@ export const synthwave84Theme: ThemePalette = { accent: "#d946a8", iconMuted: "#695d85", shadowSoft: "0 18px 48px rgba(42, 33, 57, 0.10)", + codeAnchors: { + comment: "#5a5f8a", + constant: "#a02e22", + escape: "#0a6b6a", + function: "#0a6b6a", + keyword: "#7a5c00", + markup: "#1a7a52", + parameter: "#9b1f7a", + property: "#9b1f7a", + string: "#8a3a00", + type: "#a00e1a", + typeParameter: "#a00e1a", + variable: "#9b1f7a", + }, }, dark: { bgPrimary: "#262335", @@ -29,5 +43,19 @@ export const synthwave84Theme: ThemePalette = { accent: "#ff7edb", iconMuted: "#7a73a6", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.40)", + codeAnchors: { + comment: "#848bbd", + constant: "#f97e72", + escape: "#36f9f6", + function: "#36f9f6", + keyword: "#fede5d", + markup: "#72f1b8", + parameter: "#ff7edb", + property: "#ff7edb", + string: "#ff8b39", + type: "#fe4450", + typeParameter: "#fe4450", + variable: "#ff7edb", + }, }, }; diff --git a/apps/desktop/src/app/themes/tokyoNight.ts b/apps/desktop/src/app/themes/tokyoNight.ts index 54ecfda8..8b4c5270 100644 --- a/apps/desktop/src/app/themes/tokyoNight.ts +++ b/apps/desktop/src/app/themes/tokyoNight.ts @@ -15,6 +15,20 @@ export const tokyoNightTheme: ThemePalette = { accent: "#2e7de9", iconMuted: "#6172b0", shadowSoft: "0 18px 48px rgba(52, 59, 89, 0.08)", + codeAnchors: { + comment: "#888b94", + constant: "#965027", + escape: "#363c4d", + function: "#2959aa", + keyword: "#65359d", + markup: "#2959aa", + parameter: "#8f5e15", + property: "#0f4b6e", + string: "#385f0d", + type: "#006c86", + typeParameter: "#006c86", + variable: "#343b58", + }, }, dark: { bgPrimary: "#1a1b26", @@ -29,5 +43,19 @@ export const tokyoNightTheme: ThemePalette = { accent: "#7aa2f7", iconMuted: "#8891b3", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.38)", + codeAnchors: { + comment: "#51597d", + constant: "#ff9e64", + escape: "#89ddff", + function: "#7aa2f7", + keyword: "#bb9af7", + markup: "#7aa2f7", + parameter: "#e0af68", + property: "#7dcfff", + string: "#9ece6a", + type: "#0db9d7", + typeParameter: "#0db9d7", + variable: "#c0caf5", + }, }, }; diff --git a/apps/desktop/src/app/themes/vesper.ts b/apps/desktop/src/app/themes/vesper.ts index 2e538a12..336c4eb9 100644 --- a/apps/desktop/src/app/themes/vesper.ts +++ b/apps/desktop/src/app/themes/vesper.ts @@ -15,6 +15,20 @@ export const vesperTheme: ThemePalette = { accent: "#c87830", iconMuted: "#7e7e7e", shadowSoft: "0 18px 48px rgba(26, 26, 26, 0.08)", + codeAnchors: { + comment: "#6e6e6e", + constant: "#b5530a", + escape: "#595959", + function: "#b5530a", + keyword: "#595959", + markup: "#b5530a", + parameter: "#101010", + property: "#595959", + string: "#0f7a5e", + type: "#b5530a", + typeParameter: "#b5530a", + variable: "#101010", + }, }, dark: { bgPrimary: "#101010", @@ -29,5 +43,19 @@ export const vesperTheme: ThemePalette = { accent: "#ffc799", iconMuted: "#909090", shadowSoft: "0 24px 56px rgba(0, 0, 0, 0.45)", + codeAnchors: { + comment: "#8b8b8b", + constant: "#ffc799", + escape: "#a0a0a0", + function: "#ffc799", + keyword: "#a0a0a0", + markup: "#ffc799", + parameter: "#ffffff", + property: "#a0a0a0", + string: "#99ffe4", + type: "#ffc799", + typeParameter: "#ffc799", + variable: "#ffffff", + }, }, }; diff --git a/apps/desktop/src/features/editor/Editor.tsx b/apps/desktop/src/features/editor/Editor.tsx index 156da21a..1ba32845 100644 --- a/apps/desktop/src/features/editor/Editor.tsx +++ b/apps/desktop/src/features/editor/Editor.tsx @@ -46,7 +46,6 @@ import { type Tab, type NoteTab, } from "../../app/store/editorStore"; -import { useThemeStore } from "../../app/store/themeStore"; import { useSettingsStore } from "../../app/store/settingsStore"; import { useVaultStore } from "../../app/store/vaultStore"; import { @@ -455,7 +454,6 @@ export function Editor({ ); const registerCommand = useCommandStore((s) => s.register); const unregisterCommand = useCommandStore((s) => s.unregister); - const isDark = useThemeStore((s) => s.isDark); const editorFontSize = useSettingsStore((s) => s.editorFontSize); const editorFontFamily = useSettingsStore((s) => s.editorFontFamily); const editorLineHeight = useSettingsStore((s) => s.editorLineHeight); @@ -2293,9 +2291,7 @@ export function Editor({ codeLanguages: resolveMarkdownCodeLanguage, }), baseTheme, - syntaxCompartment.of( - getSyntaxExtension(useThemeStore.getState().isDark), - ), + syntaxCompartment.of(getSyntaxExtension()), wrappingCompartment.of( getWrappingExtension( useSettingsStore.getState().lineWrapping, @@ -3040,14 +3036,14 @@ export function Editor({ ), ); - // Reconfigure syntax/live-preview only on actual tab switch — - // within the same tab the compartments are already correct. + // Reconfigure live-preview/spellcheck only on actual tab switch — + // within the same tab the compartments are already correct. The + // syntax compartment is not reconfigured here: theme changes + // propagate through CSS vars and the extension does not vary by + // tab. if (tabChanged) { view.dispatch({ effects: [ - syntaxCompartment.reconfigure( - getSyntaxExtension(useThemeStore.getState().isDark), - ), livePreviewCompartment.reconfigure( getLivePreviewExtension( handleOpenLinkContextMenu, @@ -3187,12 +3183,9 @@ export function Editor({ }; }, [activeTabInfo, isVisible, openVault]); - // Reconfigure syntax theme when isDark changes - useEffect(() => { - viewRef.current?.dispatch({ - effects: syntaxCompartment.reconfigure(getSyntaxExtension(isDark)), - }); - }, [isDark]); + // Syntax highlighting resolves through `--code-*` CSS vars now, so the + // editor repaints automatically when `applyThemeColors` updates the + // root. No compartment reconfigure is needed on `isDark` changes. // Reconfigure live preview when vault metadata or the setting changes useEffect(() => { diff --git a/apps/desktop/src/features/editor/FileTextTabView.tsx b/apps/desktop/src/features/editor/FileTextTabView.tsx index a6e8b3ec..32320c50 100644 --- a/apps/desktop/src/features/editor/FileTextTabView.tsx +++ b/apps/desktop/src/features/editor/FileTextTabView.tsx @@ -27,7 +27,6 @@ import { } from "../../components/context-menu/ContextMenu"; import { useEditorStore } from "../../app/store/editorStore"; import { useSettingsStore } from "../../app/store/settingsStore"; -import { useThemeStore } from "../../app/store/themeStore"; import { useVaultStore } from "../../app/store/vaultStore"; import { baseTheme, @@ -108,7 +107,6 @@ export function FileTextTabView({ paneId }: FileTextTabViewProps) { useState | null>(null); - const isDark = useThemeStore((s) => s.isDark); const editorFontSize = useSettingsStore((s) => s.editorFontSize); const editorFontFamily = useSettingsStore((s) => s.editorFontFamily); const editorLineHeight = useSettingsStore((s) => s.editorLineHeight); @@ -404,7 +402,7 @@ export function FileTextTabView({ paneId }: FileTextTabViewProps) { ); }, ), - syntaxCompartmentRef.current.of(getSyntaxExtension(isDark)), + syntaxCompartmentRef.current.of(getSyntaxExtension()), languageCompartmentRef.current.of([]), ], }), @@ -434,7 +432,6 @@ export function FileTextTabView({ paneId }: FileTextTabViewProps) { }, [ handleLocalContentChange, handleEditorContextMenu, - isDark, lineWrapping, syncCurrentSelection, tab, @@ -442,18 +439,10 @@ export function FileTextTabView({ paneId }: FileTextTabViewProps) { trackedFileMatch?.trackedFile.diffBase, ]); - useEffect(() => { - const view = viewRef.current; - if (!view) { - return; - } - - view.dispatch({ - effects: syntaxCompartmentRef.current.reconfigure( - getSyntaxExtension(isDark), - ), - }); - }, [isDark]); + // Syntax highlighting resolves through `--code-*` CSS vars, so no + // compartment reconfigure is needed when light/dark or the theme name + // changes — `applyThemeColors` updates the root vars and CodeMirror + // repaints automatically. useEffect(() => { const view = viewRef.current; diff --git a/apps/desktop/src/features/editor/editorExtensions.ts b/apps/desktop/src/features/editor/editorExtensions.ts index 4f2fbbdf..adcbd593 100644 --- a/apps/desktop/src/features/editor/editorExtensions.ts +++ b/apps/desktop/src/features/editor/editorExtensions.ts @@ -6,12 +6,8 @@ import { lineNumbers, } from "@codemirror/view"; import { Compartment, RangeSetBuilder } from "@codemirror/state"; -import { - syntaxTree, - syntaxHighlighting, - defaultHighlightStyle, -} from "@codemirror/language"; -import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark"; +import { syntaxTree, syntaxHighlighting } from "@codemirror/language"; +import { buildSyntaxHighlightStyle } from "./extensions/syntaxTheme"; import type { EditorFontFamily, SpellcheckLanguage, @@ -145,7 +141,11 @@ export const baseTheme = EditorView.theme({ }, }); -// Compartment for syntax highlighting (switches between dark/light) +// Compartment for syntax highlighting. Theme changes flow through +// `--code-*` CSS vars and CodeMirror repaints automatically, so this +// compartment exists only to keep the extension graph consistent with the +// other compartments and as a hook in case the extension grows tab- or +// language-dependent in the future. export const syntaxCompartment = new Compartment(); // Compartment for the live preview extension (reconfigured when vault changes) export const livePreviewCompartment = new Compartment(); @@ -204,12 +204,13 @@ const sourceHeadingDecorationExtension = ViewPlugin.fromClass( }, ); -export function getSyntaxExtension(isDark: boolean) { - // Only switch syntax highlighting colors, not the full editor theme +// Syntax highlighting reads `--code-*` CSS vars, so it does NOT depend on +// `isDark` or the active theme name. Theme switches propagate through +// `applyThemeColors` updating the CSS vars; CodeMirror repaints +// automatically without reconfiguring the compartment. +export function getSyntaxExtension() { return [ - syntaxHighlighting( - isDark ? oneDarkHighlightStyle : defaultHighlightStyle, - ), + syntaxHighlighting(buildSyntaxHighlightStyle()), sourceHeadingDecorationExtension, ]; } diff --git a/apps/desktop/src/features/editor/extensions/livePreviewTheme.ts b/apps/desktop/src/features/editor/extensions/livePreviewTheme.ts index 7d39c0b7..128d18e4 100644 --- a/apps/desktop/src/features/editor/extensions/livePreviewTheme.ts +++ b/apps/desktop/src/features/editor/extensions/livePreviewTheme.ts @@ -32,41 +32,48 @@ export const livePreviewTheme = EditorView.baseTheme({ overflow: "hidden", opacity: "0", }, + // Live preview headings pick up the per-theme `markup` anchor so they + // match the source-mode `#` color from `buildSyntaxHighlightStyle`. ".cm-lp-h1": { fontSize: "1.8em", fontWeight: "700", lineHeight: "1.3", + color: "var(--code-markup)", textDecoration: "none", }, ".cm-lp-h2": { fontSize: "1.5em", fontWeight: "600", lineHeight: "1.35", + color: "var(--code-markup)", textDecoration: "none", }, ".cm-lp-h3": { fontSize: "1.25em", fontWeight: "600", lineHeight: "1.4", + color: "var(--code-markup)", textDecoration: "none", }, ".cm-lp-h4": { fontSize: "1.1em", fontWeight: "600", lineHeight: "1.45", + color: "var(--code-markup)", textDecoration: "none", }, ".cm-lp-h5": { fontSize: "1.05em", fontWeight: "600", lineHeight: "1.5", + color: "var(--code-markup)", textDecoration: "none", }, ".cm-lp-h6": { fontSize: "1em", fontWeight: "600", lineHeight: "1.5", - color: "var(--text-secondary)", + color: "var(--code-markup)", textDecoration: "none", }, ".cm-lp-h1, .cm-lp-h1 *": { textDecoration: "none" }, @@ -81,10 +88,14 @@ export const livePreviewTheme = EditorView.baseTheme({ fontFamily: "ui-monospace, 'SF Mono', Monaco, 'Cascadia Code', monospace", fontSize: "0.9em", + color: "var(--code-string)", backgroundColor: "var(--bg-tertiary)", borderRadius: "3px", padding: "1px 4px", }, + ".cm-lp-code *": { + color: "inherit", + }, ".cm-lp-strikethrough": { textDecoration: "line-through" }, ".cm-lp-highlight": { backgroundColor: "var(--highlight-bg)", diff --git a/apps/desktop/src/features/editor/extensions/syntaxTheme.ts b/apps/desktop/src/features/editor/extensions/syntaxTheme.ts new file mode 100644 index 00000000..80daddf8 --- /dev/null +++ b/apps/desktop/src/features/editor/extensions/syntaxTheme.ts @@ -0,0 +1,181 @@ +import { HighlightStyle } from "@codemirror/language"; +import { tags as t } from "@lezer/highlight"; + +// CodeMirror HighlightStyle that references the per-theme `--code-*` CSS +// variables published by `applyThemeColors`. Because the highlight rules +// resolve through `var(...)`, switching themes only requires updating the +// CSS vars on `:root` — no need to rebuild or reconfigure the +// HighlightStyle, and `themeName` never leaks into the editor extension +// graph. Coverage mirrors the static highlighter in +// `staticCodeHighlight.tsx` so source mode and rendered code stay aligned. + +type CodeSyntaxClass = + | "comment" + | "constant" + | "escape" + | "function" + | "keyword" + | "markup" + | "parameter" + | "property" + | "string" + | "type" + | "typeParameter" + | "variable"; + +const CODE_VAR_NAME: Record = { + comment: "--code-comment", + constant: "--code-constant", + escape: "--code-escape", + function: "--code-function", + keyword: "--code-keyword", + markup: "--code-markup", + parameter: "--code-parameter", + property: "--code-property", + string: "--code-string", + type: "--code-type", + typeParameter: "--code-type-parameter", + variable: "--code-variable", +}; + +function codeVar(slot: CodeSyntaxClass): string { + return `var(${CODE_VAR_NAME[slot]})`; +} + +export function buildSyntaxHighlightStyle(): HighlightStyle { + return HighlightStyle.define([ + { + tag: [t.comment, t.lineComment, t.blockComment, t.docComment], + color: codeVar("comment"), + fontStyle: "italic", + }, + { + tag: [ + t.keyword, + t.controlKeyword, + t.operatorKeyword, + t.modifier, + t.definitionKeyword, + t.moduleKeyword, + ], + color: codeVar("keyword"), + }, + { + tag: [t.string, t.docString, t.character, t.attributeValue], + color: codeVar("string"), + }, + { + tag: [t.special(t.string), t.escape, t.regexp], + color: codeVar("escape"), + }, + { + tag: [ + t.number, + t.integer, + t.float, + t.bool, + t.atom, + t.null, + t.literal, + t.unit, + t.color, + ], + color: codeVar("constant"), + }, + { + tag: [t.typeName, t.className, t.namespace, t.macroName], + color: codeVar("type"), + }, + { tag: t.typeOperator, color: codeVar("typeParameter") }, + { + tag: [ + t.function(t.variableName), + t.function(t.propertyName), + t.function(t.className), + t.function(t.labelName), + ], + color: codeVar("function"), + }, + { + tag: [t.propertyName, t.definition(t.propertyName)], + color: codeVar("property"), + }, + { + tag: [ + t.attributeName, + t.definition(t.attributeName), + t.local(t.variableName), + ], + color: codeVar("parameter"), + }, + { + tag: [ + t.name, + t.variableName, + t.definition(t.variableName), + t.labelName, + ], + color: codeVar("variable"), + }, + { + tag: [ + t.tagName, + t.definition(t.tagName), + t.heading, + t.heading1, + t.heading2, + t.heading3, + t.heading4, + t.heading5, + t.heading6, + ], + color: codeVar("markup"), + }, + { + tag: [ + t.operator, + t.derefOperator, + t.arithmeticOperator, + t.logicOperator, + t.bitwiseOperator, + t.compareOperator, + t.updateOperator, + t.definitionOperator, + t.controlOperator, + ], + color: codeVar("keyword"), + }, + // Brackets and structural punctuation match the static highlighter's + // `cm-static-token-punctuation` bucket so the two surfaces agree. + { + tag: [ + t.punctuation, + t.separator, + t.bracket, + t.paren, + t.squareBracket, + t.brace, + t.angleBracket, + ], + color: codeVar("markup"), + }, + { tag: t.strong, fontWeight: "700" }, + { tag: t.emphasis, fontStyle: "italic" }, + { tag: t.strikethrough, textDecoration: "line-through" }, + { + tag: [t.link, t.url], + color: codeVar("function"), + textDecoration: "underline", + }, + { tag: t.monospace, color: codeVar("string") }, + { + tag: [t.meta, t.documentMeta, t.annotation, t.processingInstruction], + color: codeVar("comment"), + }, + { + tag: t.invalid, + color: codeVar("markup"), + textDecoration: "underline", + }, + ]); +} diff --git a/apps/desktop/src/index.css b/apps/desktop/src/index.css index 6a5b338b..0d22c2ec 100644 --- a/apps/desktop/src/index.css +++ b/apps/desktop/src/index.css @@ -271,6 +271,21 @@ --diff-update: #1d4ed8; --diff-warn: #92400e; --diff-move: #a16207; + + /* Syntax highlight anchors — defaults aligned with `default` theme light. + `applyThemeColors` overwrites these at runtime for the active theme. */ + --code-comment: #737373; + --code-constant: #9a3412; + --code-escape: #be185d; + --code-function: #1d4ed8; + --code-keyword: #6d28d9; + --code-markup: #b42318; + --code-parameter: #b45309; + --code-property: #1e3a8a; + --code-string: #0b6b3a; + --code-type: #8a5a00; + --code-type-parameter: #15803d; + --code-variable: #1f2937; --catppuccin-icon-rosewater: #dc8a78; --catppuccin-icon-flamingo: #dd7878; --catppuccin-icon-pink: #ea76cb; @@ -312,6 +327,20 @@ --diff-update: #60a5fa; --diff-warn: #fbbf24; --diff-move: #fcd34d; + + /* Syntax highlight anchors — defaults aligned with `default` theme dark. */ + --code-comment: #8a8a8a; + --code-constant: #fdba74; + --code-escape: #f472b6; + --code-function: #93c5fd; + --code-keyword: #c4b5fd; + --code-markup: #fca5a5; + --code-parameter: #fed7aa; + --code-property: #fda4af; + --code-string: #a7f3d0; + --code-type: #fde68a; + --code-type-parameter: #86efac; + --code-variable: #e5e7eb; --catppuccin-icon-rosewater: #f4dbd6; --catppuccin-icon-flamingo: #f0c6c6; --catppuccin-icon-pink: #f5bde6; @@ -511,67 +540,70 @@ html[data-desktop-platform="windows"].dark { } .cm-static-token-comment { - color: color-mix(in srgb, var(--text-secondary) 92%, transparent); + color: var(--code-comment); font-style: italic; } .cm-static-token-keyword { - color: color-mix(in srgb, var(--diff-update) 82%, var(--text-primary)); + color: var(--code-keyword); } .cm-static-token-variable { - color: color-mix(in srgb, var(--text-primary) 88%, var(--accent)); + color: var(--code-variable); } .cm-static-token-definition { - color: color-mix(in srgb, var(--text-primary) 65%, var(--accent)); + color: var(--code-variable); font-weight: 600; } .cm-static-token-function { - color: color-mix(in srgb, #0f766e 86%, var(--text-primary)); + color: var(--code-function); } .cm-static-token-type { - color: color-mix(in srgb, #b45309 78%, var(--text-primary)); + color: var(--code-type); } .cm-static-token-property { - color: color-mix(in srgb, var(--text-primary) 94%, transparent); + color: var(--code-property); } .cm-static-token-tag { - color: color-mix(in srgb, var(--accent) 62%, var(--text-primary)); + color: var(--code-markup); } .cm-static-token-attribute { - color: color-mix(in srgb, var(--diff-update) 70%, var(--text-primary)); + color: var(--code-parameter); } .cm-static-token-attribute-value, .cm-static-token-string { - color: color-mix(in srgb, var(--diff-add) 76%, var(--text-primary)); + color: var(--code-string); } .cm-static-token-number, .cm-static-token-atom { - color: color-mix(in srgb, var(--diff-move) 82%, var(--text-primary)); + color: var(--code-constant); +} + +.cm-static-token-operator { + color: var(--code-keyword); } -.cm-static-token-operator, .cm-static-token-punctuation { - color: color-mix(in srgb, var(--text-primary) 80%, var(--text-secondary)); + color: var(--code-markup); } .cm-static-token-meta, .cm-static-token-escape { - color: color-mix(in srgb, var(--diff-update) 66%, var(--text-secondary)); + color: var(--code-escape); } .cm-static-token-invalid { - color: var(--diff-remove); + color: var(--code-markup); text-decoration: wavy underline - color-mix(in srgb, var(--diff-remove) 72%, transparent); + color-mix(in srgb, var(--code-markup) 72%, transparent); } body.resizing-sidebar,