Skip to content

Commit c806abf

Browse files
yvbopengantfu
andauthored
feat(core): add option to merge consecutive tokens with same style (#972) (#991)
Co-authored-by: Anthony Fu <[email protected]>
1 parent 71d1b48 commit c806abf

File tree

3 files changed

+86
-0
lines changed

3 files changed

+86
-0
lines changed

packages/core/src/highlight/code-to-hast.ts

+37
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,18 @@ export function codeToHast(
4747

4848
const {
4949
mergeWhitespaces = true,
50+
mergeSameStyleTokens = false,
5051
} = options
5152

5253
if (mergeWhitespaces === true)
5354
tokens = mergeWhitespaceTokens(tokens)
5455
else if (mergeWhitespaces === 'never')
5556
tokens = splitWhitespaceTokens(tokens)
5657

58+
if (mergeSameStyleTokens) {
59+
tokens = mergeAdjacentStyledTokens(tokens)
60+
}
61+
5762
const contextSource = {
5863
...transformerContext,
5964
get source() {
@@ -306,3 +311,35 @@ function splitWhitespaceTokens(tokens: ThemedToken[][]): ThemedToken[][] {
306311
})
307312
})
308313
}
314+
315+
function mergeAdjacentStyledTokens(tokens: ThemedToken[][]): ThemedToken[][] {
316+
return tokens.map((line) => {
317+
const newLine: ThemedToken[] = []
318+
for (const token of line) {
319+
if (newLine.length === 0) {
320+
newLine.push({ ...token })
321+
continue
322+
}
323+
324+
const prevToken = newLine[newLine.length - 1]
325+
const prevStyle = prevToken.htmlStyle || stringifyTokenStyle(getTokenStyleObject(prevToken))
326+
const currentStyle = token.htmlStyle || stringifyTokenStyle(getTokenStyleObject(token))
327+
const isPrevDecorated = prevToken.fontStyle && (
328+
(prevToken.fontStyle & FontStyle.Underline)
329+
|| (prevToken.fontStyle & FontStyle.Strikethrough)
330+
)
331+
const isDecorated = token.fontStyle && (
332+
(token.fontStyle & FontStyle.Underline)
333+
|| (token.fontStyle & FontStyle.Strikethrough)
334+
)
335+
336+
if (!isPrevDecorated && !isDecorated && prevStyle === currentStyle) {
337+
prevToken.content += token.content
338+
}
339+
else {
340+
newLine.push({ ...token })
341+
}
342+
}
343+
return newLine
344+
})
345+
}

packages/core/test/hast.test.ts

+41
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,44 @@ it('render whitespace', async () => {
9494
<span class="line"><span style="color:#59873A"> tab</span><span style="color:#999999">()</span></span></code></pre>"
9595
`)
9696
})
97+
98+
describe('merge same style', () => {
99+
it('merges adjacent tokens with same style', async () => {
100+
using shiki = await createHighlighter({
101+
themes: ['min-light'],
102+
langs: ['yaml'],
103+
})
104+
105+
const code = 'name: CI'
106+
const html = await shiki.codeToHtml(code, {
107+
lang: 'yaml',
108+
theme: 'min-light',
109+
mergeSameStyleTokens: true,
110+
})
111+
112+
expect(html).toMatchInlineSnapshot(`"<pre class="shiki min-light" style="background-color:#ffffff;color:#24292eff" tabindex="0"><code><span class="line"><span style="color:#D32F2F">name:</span><span style="color:#22863A"> CI</span></span></code></pre>"`)
113+
})
114+
115+
it('does not merge tokens with decorations', async () => {
116+
using shiki = await createHighlighter({
117+
themes: ['min-light'],
118+
langs: ['yaml'],
119+
})
120+
121+
const code = 'name: CI'
122+
const html = await shiki.codeToHtml(code, {
123+
lang: 'yaml',
124+
theme: 'min-light',
125+
mergeSameStyleTokens: true,
126+
decorations: [
127+
{
128+
start: { line: 0, character: 0 },
129+
end: { line: 0, character: 4 },
130+
properties: { class: 'highlighted-word' },
131+
},
132+
],
133+
})
134+
135+
expect(html).toMatchInlineSnapshot(`"<pre class="shiki min-light" style="background-color:#ffffff;color:#24292eff" tabindex="0"><code><span class="line"><span style="color:#D32F2F" class="highlighted-word">name</span><span style="color:#D32F2F">:</span><span style="color:#22863A"> CI</span></span></code></pre>"`)
136+
})
137+
})

packages/types/src/options.ts

+8
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,14 @@ export interface CodeToHastOptionsCommon<Languages extends string = string>
142142
*/
143143
mergeWhitespaces?: boolean | 'never'
144144

145+
/**
146+
* Merge consecutive tokens with the same style to reduce the number of DOM nodes.
147+
* This can improve rendering performance but may affect the structure of the output.
148+
*
149+
* @default false
150+
*/
151+
mergeSameStyleTokens?: boolean
152+
145153
/**
146154
* The structure of the generated HAST and HTML.
147155
*

0 commit comments

Comments
 (0)