diff --git a/packages/core/src/code-to-tokens-ansi.ts b/packages/core/src/code-to-tokens-ansi.ts index b6e2b75aa..5acca9a3c 100644 --- a/packages/core/src/code-to-tokens-ansi.ts +++ b/packages/core/src/code-to-tokens-ansi.ts @@ -39,6 +39,7 @@ export function tokenizeAnsiWithTheme( } color = applyColorReplacements(color, colorReplacements) + bgColor = applyColorReplacements(bgColor, colorReplacements) if (token.decorations.has('dim')) color = dimColor(color) diff --git a/packages/core/src/code-to-tokens-base.ts b/packages/core/src/code-to-tokens-base.ts index 405c3ad7d..e7c3b12eb 100644 --- a/packages/core/src/code-to-tokens-base.ts +++ b/packages/core/src/code-to-tokens-base.ts @@ -96,14 +96,16 @@ export function tokenizeWithTheme( continue const metadata = result.tokens[2 * j + 1] - const foreground = StackElementMetadata.getForeground(metadata) - const foregroundColor = applyColorReplacements(colorMap[foreground], colorReplacements) + const color = applyColorReplacements( + colorMap[StackElementMetadata.getForeground(metadata)], + colorReplacements, + ) const fontStyle: FontStyle = StackElementMetadata.getFontStyle(metadata) const token: ThemedToken = { content: line.substring(startIndex, nextStartIndex), offset: lineOffset + startIndex, - color: foregroundColor, + color, fontStyle, } diff --git a/packages/core/src/code-to-tokens.ts b/packages/core/src/code-to-tokens.ts index 7dd361d22..e80e8de2f 100644 --- a/packages/core/src/code-to-tokens.ts +++ b/packages/core/src/code-to-tokens.ts @@ -2,7 +2,7 @@ import { codeToTokensBase } from './code-to-tokens-base' import { codeToTokensWithThemes } from './code-to-tokens-themes' import { ShikiError } from './error' import type { CodeToTokensOptions, ShikiInternal, ThemedToken, ThemedTokenWithVariants, TokensResult } from './types' -import { getTokenStyleObject, stringifyTokenStyle } from './utils' +import { applyColorReplacements, getTokenStyleObject, stringifyTokenStyle } from './utils' /** * High-level code-to-tokens API. @@ -24,6 +24,7 @@ export function codeToTokens( const { defaultColor = 'light', cssVariablePrefix = '--shiki-', + colorReplacements, } = options const themes = Object.entries(options.themes) @@ -48,12 +49,20 @@ export function codeToTokens( tokens = themeTokens .map(line => line.map(token => mergeToken(token, themesOrder, cssVariablePrefix, defaultColor))) - fg = themes.map((t, idx) => (idx === 0 && defaultColor ? '' : `${cssVariablePrefix + t.color}:`) + (themeRegs[idx].fg || 'inherit')).join(';') - bg = themes.map((t, idx) => (idx === 0 && defaultColor ? '' : `${cssVariablePrefix + t.color}-bg:`) + (themeRegs[idx].bg || 'inherit')).join(';') + fg = themes.map((t, idx) => (idx === 0 && defaultColor + ? '' + : `${cssVariablePrefix + t.color}:`) + (applyColorReplacements(themeRegs[idx].fg, colorReplacements) || 'inherit')).join(';') + bg = themes.map((t, idx) => (idx === 0 && defaultColor + ? '' + : `${cssVariablePrefix + t.color}-bg:`) + (applyColorReplacements(themeRegs[idx].bg, colorReplacements) || 'inherit')).join(';') themeName = `shiki-themes ${themeRegs.map(t => t.name).join(' ')}` rootStyle = defaultColor ? undefined : [fg, bg].join(';') } else if ('theme' in options) { + const { + colorReplacements, + } = options + tokens = codeToTokensBase( internal, code, @@ -61,8 +70,8 @@ export function codeToTokens( ) const _theme = internal.getTheme(options.theme) - bg = _theme.bg - fg = _theme.fg + bg = applyColorReplacements(_theme.bg, colorReplacements) + fg = applyColorReplacements(_theme.fg, colorReplacements) themeName = _theme.name } else { diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index d4c690aa5..e1dfd83ad 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -146,7 +146,11 @@ export function splitTokens< }) } -export function applyColorReplacements(color: string, replacements?: Record): string { +export function applyColorReplacements(color: string, replacements?: Record): string +export function applyColorReplacements(color?: string | undefined, replacements?: Record): string | undefined +export function applyColorReplacements(color?: string, replacements?: Record): string | undefined { + if (!color) + return color return replacements?.[color?.toLowerCase()] || color } diff --git a/packages/shiki/test/color-replacement.test.ts b/packages/shiki/test/color-replacement.test.ts new file mode 100644 index 000000000..90f06b3e5 --- /dev/null +++ b/packages/shiki/test/color-replacement.test.ts @@ -0,0 +1,46 @@ +import { expect, it } from 'vitest' +import { codeToHtml } from '../src' + +it('colorReplacements', async () => { + const result = await codeToHtml('console.log("hi")', { + lang: 'js', + themes: { + light: 'vitesse-light', + dark: 'material-theme-palenight', + }, + colorReplacements: { + '#393a34': 'var(---replaced-1)', + '#b07d48': 'var(---replaced-2)', + }, + }) + + expect(result).toContain('var(---replaced-1)') + expect(result).toContain('var(---replaced-2)') + + expect(result.replace(/>/g, '>\n')) + .toMatchInlineSnapshot(` + "
+      
+      
+      
+      console
+      
+      .
+      
+      log
+      
+      (
+      
+      "
+      
+      hi
+      
+      "
+      
+      )
+      
+      
+      
+ " + `) +})