Merge pull request #534 from omdsh-dev/feat/palette-single-source
refactor(theme): One Dark/Light 语法色板单源化
This commit is contained in:
+14
-13
@@ -37,6 +37,7 @@ import { FitAddon } from '@xterm/addon-fit'
|
||||
import { writeClipboard } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import '@xterm/xterm/css/xterm.css'
|
||||
import { t } from './locales.ts'
|
||||
import { ONE_DARK, ONE_LIGHT } from './one-dark-palette.ts'
|
||||
import { openWhenSized } from './open-when-sized.ts'
|
||||
import { api, type SessionScope, type TerminalDepsStatus } from './api.ts'
|
||||
import { agentUuidOf, isAgentTabId, type SidebarStore } from './state.ts'
|
||||
@@ -67,24 +68,24 @@ type TerminalDepsInfo = Extract<TerminalDepsStatus, { ok: false }>
|
||||
* Curated ANSI palettes for the terminal. The surface colors (background,
|
||||
* foreground, cursor, selection) ride the theme tokens so the terminal
|
||||
* blends with the panel in both schemes; the 16 ANSI colors are the same
|
||||
* designed palettes the app's code surfaces use (one-dark family for dark,
|
||||
* one-light family for light), read live so a scheme flip re-themes in
|
||||
* place.
|
||||
* designed syntax families the app's code surfaces use — one-dark for
|
||||
* dark, one-light for light (one-dark-palette.ts, shared with the
|
||||
* CodeMirror themes) — read live so a scheme flip re-themes in place.
|
||||
*/
|
||||
const ANSI_DARK: Record<string, string> = {
|
||||
black: '#282c34', red: '#e06c75', green: '#98c379', yellow: '#e5c07b',
|
||||
blue: '#61afef', magenta: '#c678dd', cyan: '#56b6c2', white: '#abb2bf',
|
||||
brightBlack: '#5c6370', brightRed: '#e06c75', brightGreen: '#98c379',
|
||||
brightYellow: '#e5c07b', brightBlue: '#61afef', brightMagenta: '#c678dd',
|
||||
brightCyan: '#56b6c2', brightWhite: '#ffffff',
|
||||
black: ONE_DARK.black, red: ONE_DARK.red, green: ONE_DARK.green, yellow: ONE_DARK.yellow,
|
||||
blue: ONE_DARK.blue, magenta: ONE_DARK.magenta, cyan: ONE_DARK.cyan, white: ONE_DARK.gray,
|
||||
brightBlack: ONE_DARK.faintGray, brightRed: ONE_DARK.red, brightGreen: ONE_DARK.green,
|
||||
brightYellow: ONE_DARK.yellow, brightBlue: ONE_DARK.blue, brightMagenta: ONE_DARK.magenta,
|
||||
brightCyan: ONE_DARK.cyan, brightWhite: ONE_DARK.white,
|
||||
}
|
||||
|
||||
const ANSI_LIGHT: Record<string, string> = {
|
||||
black: '#383a42', red: '#e45649', green: '#50a14f', yellow: '#c18401',
|
||||
blue: '#0184bc', magenta: '#a626a4', cyan: '#0997b3', white: '#a0a1a7',
|
||||
brightBlack: '#4f525e', brightRed: '#e45649', brightGreen: '#50a14f',
|
||||
brightYellow: '#c18401', brightBlue: '#0184bc', brightMagenta: '#a626a4',
|
||||
brightCyan: '#0997b3', brightWhite: '#fafafa',
|
||||
black: ONE_LIGHT.black, red: ONE_LIGHT.red, green: ONE_LIGHT.green, yellow: ONE_LIGHT.yellow,
|
||||
blue: ONE_LIGHT.blue, magenta: ONE_LIGHT.magenta, cyan: ONE_LIGHT.cyan, white: ONE_LIGHT.gray,
|
||||
brightBlack: ONE_LIGHT.faintGray, brightRed: ONE_LIGHT.red, brightGreen: ONE_LIGHT.green,
|
||||
brightYellow: ONE_LIGHT.yellow, brightBlue: ONE_LIGHT.blue, brightMagenta: ONE_LIGHT.magenta,
|
||||
brightCyan: ONE_LIGHT.cyan, brightWhite: ONE_LIGHT.offWhite,
|
||||
}
|
||||
|
||||
/** The xterm theme for the current scheme (surface from tokens, ANSI curated). */
|
||||
|
||||
+44
-42
@@ -2,15 +2,17 @@
|
||||
* CodeMirror 6 theme pieces for the sidebar editor. The editor surface
|
||||
* (background, caret, gutter) rides the DSH theme tokens so it blends with
|
||||
* the panel in both schemes; only the syntax token colors need concrete
|
||||
* values, and those come from the same designed palettes the app's code
|
||||
* surfaces use — the one-dark family for dark, the one-light family for
|
||||
* light. The scheme flip reconfigures these via a compartment (see
|
||||
* TextEditor), so the document, undo history and scroll survive re-theming.
|
||||
* values, and those come from the same designed syntax families the app's
|
||||
* code surfaces use — one-dark for dark, one-light for light
|
||||
* (one-dark-palette.ts, shared with the terminal's ANSI palette). The
|
||||
* scheme flip reconfigures these via a compartment (see TextEditor), so
|
||||
* the document, undo history and scroll survive re-theming.
|
||||
*/
|
||||
import { Compartment } from '@codemirror/state'
|
||||
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language'
|
||||
import { tags, type Tag } from '@lezer/highlight'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
import { ONE_DARK, ONE_LIGHT } from './one-dark-palette.ts'
|
||||
|
||||
/** Token-driven surface shared by both schemes (pure CSS values). */
|
||||
export const cmSurfaceTheme = EditorView.theme({
|
||||
@@ -53,52 +55,52 @@ interface HighlightRule {
|
||||
fontStyle?: string
|
||||
}
|
||||
|
||||
/** one-dark syntax palette (mirrors @codemirror/theme-one-dark). */
|
||||
/** one-dark syntax rules (mirrors @codemirror/theme-one-dark; hues from one-dark-palette.ts). */
|
||||
const HIGHLIGHTS_DARK: HighlightRule[] = [
|
||||
{ tag: tags.comment, color: '#5c6370', fontStyle: 'italic' },
|
||||
{ tag: tags.keyword, color: '#c678dd' },
|
||||
{ tag: tags.string, color: '#98c379' },
|
||||
{ tag: tags.number, color: '#d19a66' },
|
||||
{ tag: tags.bool, color: '#d19a66' },
|
||||
{ tag: tags.atom, color: '#d19a66' },
|
||||
{ tag: tags.typeName, color: '#e5c07b' },
|
||||
{ tag: tags.className, color: '#e5c07b' },
|
||||
{ tag: tags.propertyName, color: '#e06c75' },
|
||||
{ tag: tags.function(tags.variableName), color: '#61afef' },
|
||||
{ tag: tags.variableName, color: '#e06c75' },
|
||||
{ tag: tags.operator, color: '#56b6c2' },
|
||||
{ tag: tags.tagName, color: '#e06c75' },
|
||||
{ tag: tags.attributeName, color: '#d19a66' },
|
||||
{ tag: tags.heading, color: '#e06c75', fontStyle: 'bold' },
|
||||
{ tag: tags.comment, color: ONE_DARK.faintGray, fontStyle: 'italic' },
|
||||
{ tag: tags.keyword, color: ONE_DARK.magenta },
|
||||
{ tag: tags.string, color: ONE_DARK.green },
|
||||
{ tag: tags.number, color: ONE_DARK.orange },
|
||||
{ tag: tags.bool, color: ONE_DARK.orange },
|
||||
{ tag: tags.atom, color: ONE_DARK.orange },
|
||||
{ tag: tags.typeName, color: ONE_DARK.yellow },
|
||||
{ tag: tags.className, color: ONE_DARK.yellow },
|
||||
{ tag: tags.propertyName, color: ONE_DARK.red },
|
||||
{ tag: tags.function(tags.variableName), color: ONE_DARK.blue },
|
||||
{ tag: tags.variableName, color: ONE_DARK.red },
|
||||
{ tag: tags.operator, color: ONE_DARK.cyan },
|
||||
{ tag: tags.tagName, color: ONE_DARK.red },
|
||||
{ tag: tags.attributeName, color: ONE_DARK.orange },
|
||||
{ tag: tags.heading, color: ONE_DARK.red, fontStyle: 'bold' },
|
||||
{ tag: tags.emphasis, fontStyle: 'italic' },
|
||||
{ tag: tags.strong, fontStyle: 'bold' },
|
||||
{ tag: tags.link, color: '#61afef', fontStyle: 'underline' },
|
||||
{ tag: tags.meta, color: '#e5c07b' },
|
||||
{ tag: tags.invalid, color: '#ffffff', fontStyle: 'bold' },
|
||||
{ tag: tags.link, color: ONE_DARK.blue, fontStyle: 'underline' },
|
||||
{ tag: tags.meta, color: ONE_DARK.yellow },
|
||||
{ tag: tags.invalid, color: ONE_DARK.white, fontStyle: 'bold' },
|
||||
]
|
||||
|
||||
/** one-light syntax palette (the light counterpart of one-dark). */
|
||||
/** one-light syntax rules (the light counterpart; hues from one-dark-palette.ts). */
|
||||
const HIGHLIGHTS_LIGHT: HighlightRule[] = [
|
||||
{ tag: tags.comment, color: '#a0a1a7', fontStyle: 'italic' },
|
||||
{ tag: tags.keyword, color: '#a626a4' },
|
||||
{ tag: tags.string, color: '#50a14f' },
|
||||
{ tag: tags.number, color: '#986801' },
|
||||
{ tag: tags.bool, color: '#0184bc' },
|
||||
{ tag: tags.atom, color: '#0184bc' },
|
||||
{ tag: tags.typeName, color: '#c18401' },
|
||||
{ tag: tags.className, color: '#c18401' },
|
||||
{ tag: tags.propertyName, color: '#e45649' },
|
||||
{ tag: tags.function(tags.variableName), color: '#c18401' },
|
||||
{ tag: tags.variableName, color: '#e45649' },
|
||||
{ tag: tags.operator, color: '#383a42' },
|
||||
{ tag: tags.tagName, color: '#e45649' },
|
||||
{ tag: tags.attributeName, color: '#986801' },
|
||||
{ tag: tags.heading, color: '#e45649', fontStyle: 'bold' },
|
||||
{ tag: tags.comment, color: ONE_LIGHT.gray, fontStyle: 'italic' },
|
||||
{ tag: tags.keyword, color: ONE_LIGHT.magenta },
|
||||
{ tag: tags.string, color: ONE_LIGHT.green },
|
||||
{ tag: tags.number, color: ONE_LIGHT.orange },
|
||||
{ tag: tags.bool, color: ONE_LIGHT.blue },
|
||||
{ tag: tags.atom, color: ONE_LIGHT.blue },
|
||||
{ tag: tags.typeName, color: ONE_LIGHT.yellow },
|
||||
{ tag: tags.className, color: ONE_LIGHT.yellow },
|
||||
{ tag: tags.propertyName, color: ONE_LIGHT.red },
|
||||
{ tag: tags.function(tags.variableName), color: ONE_LIGHT.yellow },
|
||||
{ tag: tags.variableName, color: ONE_LIGHT.red },
|
||||
{ tag: tags.operator, color: ONE_LIGHT.black },
|
||||
{ tag: tags.tagName, color: ONE_LIGHT.red },
|
||||
{ tag: tags.attributeName, color: ONE_LIGHT.orange },
|
||||
{ tag: tags.heading, color: ONE_LIGHT.red, fontStyle: 'bold' },
|
||||
{ tag: tags.emphasis, fontStyle: 'italic' },
|
||||
{ tag: tags.strong, fontStyle: 'bold' },
|
||||
{ tag: tags.link, color: '#4078f2', fontStyle: 'underline' },
|
||||
{ tag: tags.meta, color: '#c18401' },
|
||||
{ tag: tags.invalid, color: '#ffffff', fontStyle: 'bold' },
|
||||
{ tag: tags.link, color: ONE_LIGHT.link, fontStyle: 'underline' },
|
||||
{ tag: tags.meta, color: ONE_LIGHT.yellow },
|
||||
{ tag: tags.invalid, color: ONE_LIGHT.white, fontStyle: 'bold' },
|
||||
]
|
||||
|
||||
/** The scheme-dependent extension pair (surface tint + syntax highlight). */
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* The single source for the syntax color families the app's code surfaces
|
||||
* use: one-dark for the dark scheme, one-light for the light scheme. Two
|
||||
* lazy-chunk consumers assemble their theme objects from these constants —
|
||||
* the terminal's curated ANSI 16 (TerminalView.tsx) and CodeMirror's
|
||||
* highlight rules (cm-themes.ts) — so a family color is defined exactly
|
||||
* once and the two views can never drift apart.
|
||||
*
|
||||
* The palette holds only the syntax hues; surface colors (background,
|
||||
* foreground, caret, gutter) are theme-token driven and selection tints /
|
||||
* token-fallback colors stay with their consumers (they are not part of
|
||||
* the designed syntax families).
|
||||
*
|
||||
* Chunk boundary: this module is inlined into BOTH lazy chunks that import
|
||||
* it (terminal and editor) — the constants are plain strings, the per-chunk
|
||||
* copy is a few hundred bytes, and no cross-chunk import is introduced. The
|
||||
* core bundle does not (and must not) import this module.
|
||||
*/
|
||||
|
||||
/** one-dark family (dark scheme) syntax hues. */
|
||||
export const ONE_DARK = {
|
||||
/** Editor-background tone; ANSI black. */
|
||||
black: '#282c34',
|
||||
/** Default foreground gray; ANSI white. */
|
||||
gray: '#abb2bf',
|
||||
/** Comment gray; ANSI bright black. */
|
||||
faintGray: '#5c6370',
|
||||
/** Pure white; ANSI bright white and the invalid-token color. */
|
||||
white: '#ffffff',
|
||||
red: '#e06c75',
|
||||
green: '#98c379',
|
||||
yellow: '#e5c07b',
|
||||
blue: '#61afef',
|
||||
magenta: '#c678dd',
|
||||
cyan: '#56b6c2',
|
||||
/** Numbers, bools, atoms, attribute names. */
|
||||
orange: '#d19a66',
|
||||
} as const
|
||||
|
||||
/** one-light family (light scheme) syntax hues. */
|
||||
export const ONE_LIGHT = {
|
||||
/** Foreground dark; ANSI black and the operator color. */
|
||||
black: '#383a42',
|
||||
/** Comment gray; ANSI white. */
|
||||
gray: '#a0a1a7',
|
||||
/** ANSI bright black. */
|
||||
faintGray: '#4f525e',
|
||||
/** Pure white; the invalid-token color (stays white in light for contrast). */
|
||||
white: '#ffffff',
|
||||
/** Off-white; ANSI bright white. */
|
||||
offWhite: '#fafafa',
|
||||
red: '#e45649',
|
||||
green: '#50a14f',
|
||||
yellow: '#c18401',
|
||||
blue: '#0184bc',
|
||||
magenta: '#a626a4',
|
||||
cyan: '#0997b3',
|
||||
/** Numbers and attribute names. */
|
||||
orange: '#986801',
|
||||
/** Markdown links — the light family's dedicated link blue (dark rides blue). */
|
||||
link: '#4078f2',
|
||||
} as const
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Byte-level guard for the shared syntax color families
|
||||
* (src/client/one-dark-palette.ts). The terminal's ANSI 16
|
||||
* (TerminalView.tsx) and CodeMirror's highlight rules (cm-themes.ts) both
|
||||
* assemble from these constants, so an accidental value edit here silently
|
||||
* re-themes every code surface in both views. The expected hexes are the
|
||||
* exact pre-single-sourcing literals from the two consumers — changing one
|
||||
* must be a deliberate, reviewable act.
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { ONE_DARK, ONE_LIGHT } from '../src/client/one-dark-palette.ts'
|
||||
|
||||
describe('one-dark-palette', () => {
|
||||
it('pins the one-dark family (dark scheme) hues', () => {
|
||||
expect(ONE_DARK).toEqual({
|
||||
black: '#282c34',
|
||||
gray: '#abb2bf',
|
||||
faintGray: '#5c6370',
|
||||
white: '#ffffff',
|
||||
red: '#e06c75',
|
||||
green: '#98c379',
|
||||
yellow: '#e5c07b',
|
||||
blue: '#61afef',
|
||||
magenta: '#c678dd',
|
||||
cyan: '#56b6c2',
|
||||
orange: '#d19a66',
|
||||
})
|
||||
})
|
||||
|
||||
it('pins the one-light family (light scheme) hues', () => {
|
||||
expect(ONE_LIGHT).toEqual({
|
||||
black: '#383a42',
|
||||
gray: '#a0a1a7',
|
||||
faintGray: '#4f525e',
|
||||
white: '#ffffff',
|
||||
offWhite: '#fafafa',
|
||||
red: '#e45649',
|
||||
green: '#50a14f',
|
||||
yellow: '#c18401',
|
||||
blue: '#0184bc',
|
||||
magenta: '#a626a4',
|
||||
cyan: '#0997b3',
|
||||
orange: '#986801',
|
||||
link: '#4078f2',
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user