Skip to content

Commit

Permalink
feat: support font-style for multiples themes
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Sep 7, 2023
1 parent 656ddd8 commit b6a42aa
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 18 deletions.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,12 @@ To make it reactive to your site's theme, you need to add a short CSS snippet:
@media (prefers-color-scheme: dark) {
.shiki,
.shiki span {
background-color: var(--shiki-dark-bg) !important;
color: var(--shiki-dark) !important;
background-color: var(--shiki-dark-bg) !important;
/* Optional, if you also want font styles */
font-style: var(--shiki-dark-font-style) !important;
font-weight: var(--shiki-dark-font-weight) !important;
text-decoration: var(--shiki-dark-text-decoration) !important;
}
}
```
Expand All @@ -289,8 +293,12 @@ To make it reactive to your site's theme, you need to add a short CSS snippet:
```css
html.dark .shiki,
html.dark .shiki span {
background-color: var(--shiki-dark-bg) !important;
color: var(--shiki-dark) !important;
background-color: var(--shiki-dark-bg) !important;
/* Optional, if you also want font styles */
font-style: var(--shiki-dark-font-style) !important;
font-weight: var(--shiki-dark-font-weight) !important;
text-decoration: var(--shiki-dark-text-decoration) !important;
}
```

Expand Down Expand Up @@ -413,7 +421,7 @@ console.log(root)
}
]
}
]s
]
}
]
}
Expand Down
58 changes: 46 additions & 12 deletions packages/shikiji/src/core/renderer-hast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,34 @@ export function codeToHast(
tokens.push(lineout)
for (let j = 0; j < lineMap[0].length; j++) {
const tokenMap = lineMap.map(t => t[j])
const colors = tokenMap.map((t, idx) => `${idx === 0 && defaultColor ? '' : `${cssVariablePrefix + themeTokens[idx][0]}:`}${t.color || 'inherit'}`).join(';')
const tokenStyles = tokenMap.map(t => getTokenStyles(t))

// Get all style keys, for themes that missing some style, we put `inherit` to override as needed
const styleKeys = new Set(tokenStyles.flatMap(t => Object.keys(t)))
const mergedStyles = tokenStyles.reduce((acc, cur, idx) => {
for (const key of styleKeys) {
const value = cur[key] || 'inherit'

if (idx === 0 && defaultColor) {
acc[key] = value
}
else {
const varKey = cssVariablePrefix + themeTokens[idx][0] + (key === 'color' ? '' : `-${key}`)
if (acc[key])
acc[key] += `;${varKey}:${value}`
else
acc[key] = `${varKey}:${value}`
}
}
return acc
}, {} as Record<string, string>)

lineout.push({
...tokenMap[0],
color: colors,
htmlStyle: defaultColor ? undefined : colors,
color: '',
htmlStyle: defaultColor
? stringifyTokenStyle(mergedStyles)
: Object.values(mergedStyles).join(';'),
})
}
}
Expand Down Expand Up @@ -134,15 +157,7 @@ export function tokensToHast(
let col = 0

for (const token of line) {
const styles = [token.htmlStyle || `color:${token.color}`]
if (token.fontStyle) {
if (token.fontStyle & FontStyle.Italic)
styles.push('font-style:italic')
if (token.fontStyle & FontStyle.Bold)
styles.push('font-weight:bold')
if (token.fontStyle & FontStyle.Underline)
styles.push('text-decoration:underline')
}
const styles = [token.htmlStyle || stringifyTokenStyle(getTokenStyles(token))]

let tokenNode: Element = {
type: 'element',
Expand Down Expand Up @@ -172,6 +187,25 @@ export function tokensToHast(
return options.transforms?.root?.(tree) || tree
}

function getTokenStyles(token: ThemedToken) {
const styles: Record<string, string> = {}
if (token.color)
styles.color = token.color
if (token.fontStyle) {
if (token.fontStyle & FontStyle.Italic)
styles['font-style'] = 'italic'
if (token.fontStyle & FontStyle.Bold)
styles['font-weight'] = 'bold'
if (token.fontStyle & FontStyle.Underline)
styles['text-decoration'] = 'underline'
}
return styles
}

function stringifyTokenStyle(token: Record<string, string>) {
return Object.entries(token).map(([key, value]) => `${key}:${value}`).join(';')
}

function mergeWhitespaceTokens(tokens: ThemedToken[][]) {
return tokens.map((line) => {
const newLine: ThemedToken[] = []
Expand Down
29 changes: 27 additions & 2 deletions packages/shikiji/test/out/multiple-themes.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,68 @@
[data-theme="light"] .shiki span {
background-color: var(--s-light-bg) !important;
color: var(--s-light) !important;
font-style: var(--s-light-font-style) !important;
font-weight: var(--s-light-font-weight) !important;
text-decoration: var(--s-light-text-decoration) !important;
}


[data-theme="dark"] .shiki,
[data-theme="dark"] .shiki span {
background-color: var(--s-dark-bg) !important;
color: var(--s-dark) !important;
font-style: var(--s-dark-font-style) !important;
font-weight: var(--s-dark-font-weight) !important;
text-decoration: var(--s-dark-text-decoration) !important;
}


[data-theme="nord"] .shiki,
[data-theme="nord"] .shiki span {
background-color: var(--s-nord-bg) !important;
color: var(--s-nord) !important;
font-style: var(--s-nord-font-style) !important;
font-weight: var(--s-nord-font-weight) !important;
text-decoration: var(--s-nord-text-decoration) !important;
}


[data-theme="min-dark"] .shiki,
[data-theme="min-dark"] .shiki span {
background-color: var(--s-min-dark-bg) !important;
color: var(--s-min-dark) !important;
font-style: var(--s-min-dark-font-style) !important;
font-weight: var(--s-min-dark-font-weight) !important;
text-decoration: var(--s-min-dark-text-decoration) !important;
}


[data-theme="min-light"] .shiki,
[data-theme="min-light"] .shiki span {
background-color: var(--s-min-light-bg) !important;
color: var(--s-min-light) !important;
font-style: var(--s-min-light-font-style) !important;
font-weight: var(--s-min-light-font-weight) !important;
text-decoration: var(--s-min-light-text-decoration) !important;
}


[data-theme="palenight"] .shiki,
[data-theme="palenight"] .shiki span {
background-color: var(--s-palenight-bg) !important;
color: var(--s-palenight) !important;
font-style: var(--s-palenight-font-style) !important;
font-weight: var(--s-palenight-font-weight) !important;
text-decoration: var(--s-palenight-text-decoration) !important;
}

</style>
<script>
const themes = ["light","dark","nord","min-dark","min-light"]
const themes = ["light","dark","nord","min-dark","min-light","palenight"]

function toggleTheme() {
document.body.dataset.theme = themes[(Math.max(themes.indexOf(document.body.dataset.theme), 0) + 1) % themes.length]
}
</script>
<button onclick="toggleTheme()">Toggle theme</button>
<pre class="shiki shiki-themes vitesse-light vitesse-dark nord min-dark min-light" style="background-color:#ffffff;--s-dark-bg:#121212;--s-nord-bg:#2e3440ff;--s-min-dark-bg:#1f1f1f;--s-min-light-bg:#ffffff;color:#393a34;--s-dark:#dbd7caee;--s-nord:#d8dee9ff;--s-min-dark:#b392f0;--s-min-light:#24292eff" tabindex="0"><code><span class="line"><span style="color:#B07D48;--s-dark:#BD976A;--s-nord:#D8DEE9;--s-min-dark:#79B8FF;--s-min-light:#1976D2">console</span><span style="color:#999999;--s-dark:#666666;--s-nord:#ECEFF4;--s-min-dark:#B392F0;--s-min-light:#6F42C1">.</span><span style="color:#59873A;--s-dark:#80A665;--s-nord:#88C0D0;--s-min-dark:#B392F0;--s-min-light:#6F42C1">log</span><span style="color:#999999;--s-dark:#666666;--s-nord:#D8DEE9FF;--s-min-dark:#B392F0;--s-min-light:#24292EFF">(</span><span style="color:#B5695999;--s-dark:#C98A7D99;--s-nord:#ECEFF4;--s-min-dark:#FFAB70;--s-min-light:#22863A">"</span><span style="color:#B56959;--s-dark:#C98A7D;--s-nord:#A3BE8C;--s-min-dark:#FFAB70;--s-min-light:#22863A">hello</span><span style="color:#B5695999;--s-dark:#C98A7D99;--s-nord:#ECEFF4;--s-min-dark:#FFAB70;--s-min-light:#22863A">"</span><span style="color:#999999;--s-dark:#666666;--s-nord:#D8DEE9FF;--s-min-dark:#B392F0;--s-min-light:#24292EFF">)</span></span></code></pre>
<pre class="shiki shiki-themes vitesse-light vitesse-dark nord min-dark min-light material-theme-palenight" style="background-color:#ffffff;--s-dark-bg:#121212;--s-nord-bg:#2e3440ff;--s-min-dark-bg:#1f1f1f;--s-min-light-bg:#ffffff;--s-palenight-bg:#292D3E;color:#393a34;--s-dark:#dbd7caee;--s-nord:#d8dee9ff;--s-min-dark:#b392f0;--s-min-light:#24292eff;--s-palenight:#babed8" tabindex="0"><code><span class="line"><span style="color:#1E754F;--s-dark:#4D9375;--s-nord:#81A1C1;--s-min-dark:#F97583;--s-min-light:#D32F2F;--s-palenight:#89DDFF;font-style:inherit;--s-dark-font-style:inherit;--s-nord-font-style:inherit;--s-min-dark-font-style:inherit;--s-min-light-font-style:inherit;--s-palenight-font-style:italic">import</span><span style="color:#1E754F;--s-dark:#4D9375;--s-nord:#81A1C1;--s-min-dark:#79B8FF;--s-min-light:#1976D2;--s-palenight:#89DDFF"> *</span><span style="color:#1E754F;--s-dark:#4D9375;--s-nord:#81A1C1;--s-min-dark:#F97583;--s-min-light:#D32F2F;--s-palenight:#89DDFF;font-style:inherit;--s-dark-font-style:inherit;--s-nord-font-style:inherit;--s-min-dark-font-style:inherit;--s-min-light-font-style:inherit;--s-palenight-font-style:italic"> as</span><span style="color:#B07D48;--s-dark:#BD976A;--s-nord:#8FBCBB;--s-min-dark:#B392F0;--s-min-light:#24292EFF;--s-palenight:#BABED8"> Shiki</span><span style="color:#1E754F;--s-dark:#4D9375;--s-nord:#81A1C1;--s-min-dark:#F97583;--s-min-light:#D32F2F;--s-palenight:#89DDFF;font-style:inherit;--s-dark-font-style:inherit;--s-nord-font-style:inherit;--s-min-dark-font-style:inherit;--s-min-light-font-style:inherit;--s-palenight-font-style:italic"> from</span><span style="color:#B5695999;--s-dark:#C98A7D99;--s-nord:#ECEFF4;--s-min-dark:#FFAB70;--s-min-light:#22863A;--s-palenight:#89DDFF"> "</span><span style="color:#B56959;--s-dark:#C98A7D;--s-nord:#A3BE8C;--s-min-dark:#FFAB70;--s-min-light:#22863A;--s-palenight:#C3E88D">shikiji</span><span style="color:#B5695999;--s-dark:#C98A7D99;--s-nord:#ECEFF4;--s-min-dark:#FFAB70;--s-min-light:#22863A;--s-palenight:#89DDFF">"</span></span></code></pre>
32 changes: 31 additions & 1 deletion packages/shikiji/test/themes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,10 @@ describe('codeToHtml', () => {
'nord': 'nord',
'min-dark': 'min-dark',
'min-light': 'min-light',
'palenight': 'material-theme-palenight',
} as const

const code = await codeToHtml('console.log("hello")', {
const code = await codeToHtml('import * as Shiki from "shikiji"', {
lang: 'js',
themes,
cssVariablePrefix: '--s-',
Expand All @@ -100,6 +101,9 @@ ${Object.keys(themes).map(theme => `
[data-theme="${theme}"] .shiki span {
background-color: var(--s-${theme}-bg) !important;
color: var(--s-${theme}) !important;
font-style: var(--s-${theme}-font-style) !important;
font-weight: var(--s-${theme}-font-weight) !important;
text-decoration: var(--s-${theme}-text-decoration) !important;
}
`).join('\n')}
</style>
Expand Down Expand Up @@ -167,6 +171,32 @@ function toggleTheme() {
expect(snippet + code)
.toMatchFileSnapshot('./out/multiple-themes-no-default.html')
})

it('should support font style', async () => {
const input = 'import * as Shiki from \'shiki\';\n'
const code1 = await codeToHtml(input, {
lang: 'js',
themes: {
light: 'material-theme-palenight',
dark: 'nord',
},
})

expect(code1)
.toContain('font-style:italic;--shiki-dark-font-style:inherit')

const code2 = await codeToHtml(input, {
lang: 'js',
themes: {
light: 'material-theme-palenight',
dark: 'nord',
},
defaultColor: 'dark',
})

expect(code2)
.toContain('font-style:inherit;--shiki-light-font-style:italic')
})
})

describe('codeToTokensWithThemes', () => {
Expand Down

0 comments on commit b6a42aa

Please sign in to comment.