Skip to content

Commit b6a42aa

Browse files
committed
feat: support font-style for multiples themes
1 parent 656ddd8 commit b6a42aa

File tree

4 files changed

+115
-18
lines changed

4 files changed

+115
-18
lines changed

README.md

+11-3
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,12 @@ To make it reactive to your site's theme, you need to add a short CSS snippet:
278278
@media (prefers-color-scheme: dark) {
279279
.shiki,
280280
.shiki span {
281-
background-color: var(--shiki-dark-bg) !important;
282281
color: var(--shiki-dark) !important;
282+
background-color: var(--shiki-dark-bg) !important;
283+
/* Optional, if you also want font styles */
284+
font-style: var(--shiki-dark-font-style) !important;
285+
font-weight: var(--shiki-dark-font-weight) !important;
286+
text-decoration: var(--shiki-dark-text-decoration) !important;
283287
}
284288
}
285289
```
@@ -289,8 +293,12 @@ To make it reactive to your site's theme, you need to add a short CSS snippet:
289293
```css
290294
html.dark .shiki,
291295
html.dark .shiki span {
292-
background-color: var(--shiki-dark-bg) !important;
293296
color: var(--shiki-dark) !important;
297+
background-color: var(--shiki-dark-bg) !important;
298+
/* Optional, if you also want font styles */
299+
font-style: var(--shiki-dark-font-style) !important;
300+
font-weight: var(--shiki-dark-font-weight) !important;
301+
text-decoration: var(--shiki-dark-text-decoration) !important;
294302
}
295303
```
296304

@@ -413,7 +421,7 @@ console.log(root)
413421
}
414422
]
415423
}
416-
]s
424+
]
417425
}
418426
]
419427
}

packages/shikiji/src/core/renderer-hast.ts

+46-12
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,34 @@ export function codeToHast(
4747
tokens.push(lineout)
4848
for (let j = 0; j < lineMap[0].length; j++) {
4949
const tokenMap = lineMap.map(t => t[j])
50-
const colors = tokenMap.map((t, idx) => `${idx === 0 && defaultColor ? '' : `${cssVariablePrefix + themeTokens[idx][0]}:`}${t.color || 'inherit'}`).join(';')
50+
const tokenStyles = tokenMap.map(t => getTokenStyles(t))
51+
52+
// Get all style keys, for themes that missing some style, we put `inherit` to override as needed
53+
const styleKeys = new Set(tokenStyles.flatMap(t => Object.keys(t)))
54+
const mergedStyles = tokenStyles.reduce((acc, cur, idx) => {
55+
for (const key of styleKeys) {
56+
const value = cur[key] || 'inherit'
57+
58+
if (idx === 0 && defaultColor) {
59+
acc[key] = value
60+
}
61+
else {
62+
const varKey = cssVariablePrefix + themeTokens[idx][0] + (key === 'color' ? '' : `-${key}`)
63+
if (acc[key])
64+
acc[key] += `;${varKey}:${value}`
65+
else
66+
acc[key] = `${varKey}:${value}`
67+
}
68+
}
69+
return acc
70+
}, {} as Record<string, string>)
71+
5172
lineout.push({
5273
...tokenMap[0],
53-
color: colors,
54-
htmlStyle: defaultColor ? undefined : colors,
74+
color: '',
75+
htmlStyle: defaultColor
76+
? stringifyTokenStyle(mergedStyles)
77+
: Object.values(mergedStyles).join(';'),
5578
})
5679
}
5780
}
@@ -134,15 +157,7 @@ export function tokensToHast(
134157
let col = 0
135158

136159
for (const token of line) {
137-
const styles = [token.htmlStyle || `color:${token.color}`]
138-
if (token.fontStyle) {
139-
if (token.fontStyle & FontStyle.Italic)
140-
styles.push('font-style:italic')
141-
if (token.fontStyle & FontStyle.Bold)
142-
styles.push('font-weight:bold')
143-
if (token.fontStyle & FontStyle.Underline)
144-
styles.push('text-decoration:underline')
145-
}
160+
const styles = [token.htmlStyle || stringifyTokenStyle(getTokenStyles(token))]
146161

147162
let tokenNode: Element = {
148163
type: 'element',
@@ -172,6 +187,25 @@ export function tokensToHast(
172187
return options.transforms?.root?.(tree) || tree
173188
}
174189

190+
function getTokenStyles(token: ThemedToken) {
191+
const styles: Record<string, string> = {}
192+
if (token.color)
193+
styles.color = token.color
194+
if (token.fontStyle) {
195+
if (token.fontStyle & FontStyle.Italic)
196+
styles['font-style'] = 'italic'
197+
if (token.fontStyle & FontStyle.Bold)
198+
styles['font-weight'] = 'bold'
199+
if (token.fontStyle & FontStyle.Underline)
200+
styles['text-decoration'] = 'underline'
201+
}
202+
return styles
203+
}
204+
205+
function stringifyTokenStyle(token: Record<string, string>) {
206+
return Object.entries(token).map(([key, value]) => `${key}:${value}`).join(';')
207+
}
208+
175209
function mergeWhitespaceTokens(tokens: ThemedToken[][]) {
176210
return tokens.map((line) => {
177211
const newLine: ThemedToken[] = []

packages/shikiji/test/out/multiple-themes.html

+27-2
Original file line numberDiff line numberDiff line change
@@ -10,43 +10,68 @@
1010
[data-theme="light"] .shiki span {
1111
background-color: var(--s-light-bg) !important;
1212
color: var(--s-light) !important;
13+
font-style: var(--s-light-font-style) !important;
14+
font-weight: var(--s-light-font-weight) !important;
15+
text-decoration: var(--s-light-text-decoration) !important;
1316
}
1417

1518

1619
[data-theme="dark"] .shiki,
1720
[data-theme="dark"] .shiki span {
1821
background-color: var(--s-dark-bg) !important;
1922
color: var(--s-dark) !important;
23+
font-style: var(--s-dark-font-style) !important;
24+
font-weight: var(--s-dark-font-weight) !important;
25+
text-decoration: var(--s-dark-text-decoration) !important;
2026
}
2127

2228

2329
[data-theme="nord"] .shiki,
2430
[data-theme="nord"] .shiki span {
2531
background-color: var(--s-nord-bg) !important;
2632
color: var(--s-nord) !important;
33+
font-style: var(--s-nord-font-style) !important;
34+
font-weight: var(--s-nord-font-weight) !important;
35+
text-decoration: var(--s-nord-text-decoration) !important;
2736
}
2837

2938

3039
[data-theme="min-dark"] .shiki,
3140
[data-theme="min-dark"] .shiki span {
3241
background-color: var(--s-min-dark-bg) !important;
3342
color: var(--s-min-dark) !important;
43+
font-style: var(--s-min-dark-font-style) !important;
44+
font-weight: var(--s-min-dark-font-weight) !important;
45+
text-decoration: var(--s-min-dark-text-decoration) !important;
3446
}
3547

3648

3749
[data-theme="min-light"] .shiki,
3850
[data-theme="min-light"] .shiki span {
3951
background-color: var(--s-min-light-bg) !important;
4052
color: var(--s-min-light) !important;
53+
font-style: var(--s-min-light-font-style) !important;
54+
font-weight: var(--s-min-light-font-weight) !important;
55+
text-decoration: var(--s-min-light-text-decoration) !important;
56+
}
57+
58+
59+
[data-theme="palenight"] .shiki,
60+
[data-theme="palenight"] .shiki span {
61+
background-color: var(--s-palenight-bg) !important;
62+
color: var(--s-palenight) !important;
63+
font-style: var(--s-palenight-font-style) !important;
64+
font-weight: var(--s-palenight-font-weight) !important;
65+
text-decoration: var(--s-palenight-text-decoration) !important;
4166
}
4267

4368
</style>
4469
<script>
45-
const themes = ["light","dark","nord","min-dark","min-light"]
70+
const themes = ["light","dark","nord","min-dark","min-light","palenight"]
4671

4772
function toggleTheme() {
4873
document.body.dataset.theme = themes[(Math.max(themes.indexOf(document.body.dataset.theme), 0) + 1) % themes.length]
4974
}
5075
</script>
5176
<button onclick="toggleTheme()">Toggle theme</button>
52-
<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>
77+
<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>

packages/shikiji/test/themes.test.ts

+31-1
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,10 @@ describe('codeToHtml', () => {
8080
'nord': 'nord',
8181
'min-dark': 'min-dark',
8282
'min-light': 'min-light',
83+
'palenight': 'material-theme-palenight',
8384
} as const
8485

85-
const code = await codeToHtml('console.log("hello")', {
86+
const code = await codeToHtml('import * as Shiki from "shikiji"', {
8687
lang: 'js',
8788
themes,
8889
cssVariablePrefix: '--s-',
@@ -100,6 +101,9 @@ ${Object.keys(themes).map(theme => `
100101
[data-theme="${theme}"] .shiki span {
101102
background-color: var(--s-${theme}-bg) !important;
102103
color: var(--s-${theme}) !important;
104+
font-style: var(--s-${theme}-font-style) !important;
105+
font-weight: var(--s-${theme}-font-weight) !important;
106+
text-decoration: var(--s-${theme}-text-decoration) !important;
103107
}
104108
`).join('\n')}
105109
</style>
@@ -167,6 +171,32 @@ function toggleTheme() {
167171
expect(snippet + code)
168172
.toMatchFileSnapshot('./out/multiple-themes-no-default.html')
169173
})
174+
175+
it('should support font style', async () => {
176+
const input = 'import * as Shiki from \'shiki\';\n'
177+
const code1 = await codeToHtml(input, {
178+
lang: 'js',
179+
themes: {
180+
light: 'material-theme-palenight',
181+
dark: 'nord',
182+
},
183+
})
184+
185+
expect(code1)
186+
.toContain('font-style:italic;--shiki-dark-font-style:inherit')
187+
188+
const code2 = await codeToHtml(input, {
189+
lang: 'js',
190+
themes: {
191+
light: 'material-theme-palenight',
192+
dark: 'nord',
193+
},
194+
defaultColor: 'dark',
195+
})
196+
197+
expect(code2)
198+
.toContain('font-style:inherit;--shiki-light-font-style:italic')
199+
})
170200
})
171201

172202
describe('codeToTokensWithThemes', () => {

0 commit comments

Comments
 (0)