Merge remote-tracking branch 'origin/main' into bb/vscode-marketplace-themes
# Conflicts: # apps/desktop/electron/main.cjs # apps/desktop/src/app/command-palette/index.tsx # apps/desktop/src/themes/context.tsx
This commit is contained in:
@@ -252,7 +252,15 @@ interface ThemeContextValue {
|
||||
theme: DesktopTheme
|
||||
themeName: string
|
||||
mode: ThemeMode
|
||||
/** The light/dark switch the user picked. */
|
||||
resolvedMode: 'light' | 'dark'
|
||||
/**
|
||||
* The mode actually painted, derived from the active background's luminance.
|
||||
* Differs from `resolvedMode` for skins that keep a bright surface in "dark"
|
||||
* (or vice-versa). Surface-bound UI (e.g. the terminal palette) should key off
|
||||
* this so it matches what's on screen instead of inverting.
|
||||
*/
|
||||
renderedMode: 'light' | 'dark'
|
||||
availableThemes: Array<{ name: string; label: string; description: string }>
|
||||
setTheme: (name: string) => void
|
||||
setMode: (mode: ThemeMode) => void
|
||||
@@ -265,6 +273,7 @@ const ThemeContext = createContext<ThemeContextValue>({
|
||||
themeName: DEFAULT_SKIN_NAME,
|
||||
mode: 'light',
|
||||
resolvedMode: 'light',
|
||||
renderedMode: 'light',
|
||||
availableThemes: SKIN_LIST,
|
||||
setTheme: () => {},
|
||||
setMode: () => {}
|
||||
@@ -310,6 +319,12 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
|
||||
const resolvedMode = resolveMode(mode, systemDark)
|
||||
const activeTheme = useMemo(() => deriveTheme(themeName, resolvedMode), [themeName, resolvedMode])
|
||||
|
||||
// What actually gets painted (matches the `.dark` class applyTheme toggles).
|
||||
const renderedMode = useMemo(
|
||||
() => renderedModeFor(activeTheme.colors, resolvedMode),
|
||||
[activeTheme, resolvedMode]
|
||||
)
|
||||
|
||||
useEffect(() => applyTheme(activeTheme, resolvedMode), [activeTheme, resolvedMode])
|
||||
|
||||
// Assign to whichever profile is live right now (read fresh so the callbacks
|
||||
@@ -331,8 +346,8 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
|
||||
// (`appearance.toggleMode`) so it shows up in the hotkey map and is rebindable.
|
||||
|
||||
const value = useMemo<ThemeContextValue>(
|
||||
() => ({ theme: activeTheme, themeName, mode, resolvedMode, availableThemes, setTheme, setMode }),
|
||||
[activeTheme, themeName, mode, resolvedMode, availableThemes, setTheme, setMode]
|
||||
() => ({ theme: activeTheme, themeName, mode, resolvedMode, renderedMode, availableThemes, setTheme, setMode }),
|
||||
[activeTheme, themeName, mode, resolvedMode, renderedMode, availableThemes, setTheme, setMode]
|
||||
)
|
||||
|
||||
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
|
||||
|
||||
@@ -48,19 +48,27 @@ export function buildThemeFromMarketplace(result: DesktopMarketplaceThemeResult)
|
||||
const label = file.label || raw.name || result.displayName
|
||||
const { mode, theme } = convertVscodeColorTheme(raw, { label, source: result.extensionId })
|
||||
|
||||
return { mode, palette: theme.colors }
|
||||
return { mode, palette: theme.colors, terminal: theme.terminal }
|
||||
})
|
||||
|
||||
const fallback = variants[0].palette
|
||||
const light = variants.find(variant => variant.mode === 'light')?.palette
|
||||
const dark = variants.find(variant => variant.mode === 'dark')?.palette
|
||||
const fallback = variants[0]
|
||||
const light = variants.find(variant => variant.mode === 'light') ?? fallback
|
||||
const dark = variants.find(variant => variant.mode === 'dark') ?? fallback
|
||||
|
||||
// The terminal ANSI palette tracks the painted variant the same way colors do
|
||||
// (light → terminal, dark → darkTerminal); each falls back to the other so a
|
||||
// single-variant import still themes the terminal in both modes.
|
||||
const terminal = light.terminal ?? dark.terminal
|
||||
const darkTerminal = dark.terminal ?? light.terminal
|
||||
|
||||
return {
|
||||
name: vscodeThemeSlug(result.displayName),
|
||||
label: result.displayName,
|
||||
description: `VS Code · ${result.extensionId}`,
|
||||
colors: light ?? dark ?? fallback,
|
||||
darkColors: dark ?? light ?? fallback
|
||||
colors: light.palette,
|
||||
darkColors: dark.palette,
|
||||
...(terminal ? { terminal } : {}),
|
||||
...(darkTerminal ? { darkTerminal } : {})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,37 @@ export interface DesktopThemeTypography {
|
||||
fontUrl?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Integrated-terminal ANSI palette (xterm `ITheme`, minus `background`).
|
||||
*
|
||||
* Populated only when a converted VS Code theme ships a full `terminal.ansi*`
|
||||
* set; otherwise the terminal keeps its built-in VS Code default palette.
|
||||
* `background` is intentionally absent — the pane always paints the live skin
|
||||
* surface so it stays translucent.
|
||||
*/
|
||||
export interface DesktopTerminalPalette {
|
||||
foreground?: string
|
||||
cursor?: string
|
||||
/** Keeps its source alpha — xterm blends it over the surface. */
|
||||
selectionBackground?: string
|
||||
black?: string
|
||||
red?: string
|
||||
green?: string
|
||||
yellow?: string
|
||||
blue?: string
|
||||
magenta?: string
|
||||
cyan?: string
|
||||
white?: string
|
||||
brightBlack?: string
|
||||
brightRed?: string
|
||||
brightGreen?: string
|
||||
brightYellow?: string
|
||||
brightBlue?: string
|
||||
brightMagenta?: string
|
||||
brightCyan?: string
|
||||
brightWhite?: string
|
||||
}
|
||||
|
||||
export interface DesktopTheme {
|
||||
name: string
|
||||
label: string
|
||||
@@ -63,4 +94,8 @@ export interface DesktopTheme {
|
||||
/** Hand-tuned dark palette. Skins like `nous` ship one. */
|
||||
darkColors?: DesktopThemeColors
|
||||
typography?: Partial<DesktopThemeTypography>
|
||||
/** Light-variant terminal ANSI palette (also the fallback for dark). */
|
||||
terminal?: DesktopTerminalPalette
|
||||
/** Dark-variant terminal ANSI palette. Falls back to `terminal`. */
|
||||
darkTerminal?: DesktopTerminalPalette
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
import { ensureContrast, luminance, mix, normalizeHex, readableOn } from './color'
|
||||
import type { DesktopTheme, DesktopThemeColors } from './types'
|
||||
import type { DesktopTerminalPalette, DesktopTheme, DesktopThemeColors } from './types'
|
||||
|
||||
// Section headers / sidebar labels render in --theme-primary directly on the
|
||||
// sidebar surface as small (~10px) uppercase text, so the accent has to clear
|
||||
@@ -101,6 +101,85 @@ const isDarkType = (raw: VscodeColorTheme, background: string): boolean => {
|
||||
return luminance(background) < 0.4
|
||||
}
|
||||
|
||||
// xterm ITheme ANSI slots ← VS Code `terminal.ansi*` tokens. Background is
|
||||
// deliberately excluded — the pane keeps the live skin surface (transparency).
|
||||
const ANSI_TOKENS: ReadonlyArray<readonly [keyof DesktopTerminalPalette, string]> = [
|
||||
['black', 'terminal.ansiBlack'],
|
||||
['red', 'terminal.ansiRed'],
|
||||
['green', 'terminal.ansiGreen'],
|
||||
['yellow', 'terminal.ansiYellow'],
|
||||
['blue', 'terminal.ansiBlue'],
|
||||
['magenta', 'terminal.ansiMagenta'],
|
||||
['cyan', 'terminal.ansiCyan'],
|
||||
['white', 'terminal.ansiWhite'],
|
||||
['brightBlack', 'terminal.ansiBrightBlack'],
|
||||
['brightRed', 'terminal.ansiBrightRed'],
|
||||
['brightGreen', 'terminal.ansiBrightGreen'],
|
||||
['brightYellow', 'terminal.ansiBrightYellow'],
|
||||
['brightBlue', 'terminal.ansiBrightBlue'],
|
||||
['brightMagenta', 'terminal.ansiBrightMagenta'],
|
||||
['brightCyan', 'terminal.ansiBrightCyan'],
|
||||
['brightWhite', 'terminal.ansiBrightWhite']
|
||||
]
|
||||
|
||||
const BASE_ANSI: ReadonlyArray<keyof DesktopTerminalPalette> = [
|
||||
'black',
|
||||
'red',
|
||||
'green',
|
||||
'yellow',
|
||||
'blue',
|
||||
'magenta',
|
||||
'cyan',
|
||||
'white'
|
||||
]
|
||||
|
||||
const HEX_RE = /^#[0-9a-f]{3,8}$/i
|
||||
|
||||
/**
|
||||
* Lift a theme's integrated-terminal ANSI palette, if it ships one.
|
||||
*
|
||||
* All-or-nothing on the base-8 colors: a half-filled palette mixed with our
|
||||
* defaults reads worse than just keeping the defaults, so we adopt the theme's
|
||||
* palette only when the full base set is present. ANSI slots flatten alpha over
|
||||
* the editor background; selection keeps its alpha so xterm can blend it.
|
||||
*/
|
||||
function extractTerminalPalette(colors: Record<string, unknown>, background: string): DesktopTerminalPalette | undefined {
|
||||
const hex = (key: string): string | undefined =>
|
||||
normalizeHex(typeof colors[key] === 'string' ? (colors[key] as string) : null, background) ?? undefined
|
||||
|
||||
const palette: DesktopTerminalPalette = {}
|
||||
|
||||
for (const [slot, token] of ANSI_TOKENS) {
|
||||
const value = hex(token)
|
||||
|
||||
if (value) {
|
||||
palette[slot] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (!BASE_ANSI.every(slot => palette[slot])) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const foreground = hex('terminal.foreground')
|
||||
const cursor = hex('terminalCursor.foreground') ?? hex('terminalCursor.background')
|
||||
const selection = typeof colors['terminal.selectionBackground'] === 'string' ? colors['terminal.selectionBackground'].trim() : ''
|
||||
|
||||
if (foreground) {
|
||||
palette.foreground = foreground
|
||||
}
|
||||
|
||||
if (cursor) {
|
||||
palette.cursor = cursor
|
||||
}
|
||||
|
||||
if (HEX_RE.test(selection)) {
|
||||
palette.selectionBackground = selection
|
||||
}
|
||||
|
||||
return palette
|
||||
}
|
||||
|
||||
/** First normalizable hex among `keys`, composited over `backdrop`. */
|
||||
const pick = (
|
||||
colors: Record<string, unknown>,
|
||||
@@ -242,6 +321,7 @@ export function convertVscodeColorTheme(raw: VscodeColorTheme, opts: ConvertOpti
|
||||
|
||||
const label = (opts.label ?? raw.name ?? 'VS Code Theme').trim()
|
||||
const slug = opts.slug ?? vscodeThemeSlug(label)
|
||||
const terminal = extractTerminalPalette(colors, background)
|
||||
|
||||
return {
|
||||
derived,
|
||||
@@ -254,7 +334,10 @@ export function convertVscodeColorTheme(raw: VscodeColorTheme, opts: ConvertOpti
|
||||
// that have both a light and dark variant (a Marketplace extension family)
|
||||
// recombine them into proper colors/darkColors via buildThemeFromMarketplace.
|
||||
colors: palette,
|
||||
darkColors: palette
|
||||
darkColors: palette,
|
||||
// Only set when the theme ships a full ANSI palette — the terminal keeps
|
||||
// its built-in VS Code defaults otherwise.
|
||||
...(terminal ? { terminal } : {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user