Skip to content

Commit ae516ae

Browse files
authored
feat(core): new structure: inline option for codeToHast (#653)
1 parent 71257dd commit ae516ae

35 files changed

+9674
-10550
lines changed

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

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,15 @@ export function tokensToHast(
7777
const transformers = getTransformers(options)
7878

7979
const lines: (Element | Text)[] = []
80-
const tree: Root = {
80+
const root: Root = {
8181
type: 'root',
8282
children: [],
8383
}
8484

85+
const {
86+
structure = 'classic',
87+
} = options
88+
8589
let preNode: Element = {
8690
type: 'element',
8791
tagName: 'pre',
@@ -110,6 +114,7 @@ export function tokensToHast(
110114

111115
const context: ShikiTransformerContext = {
112116
...transformerContext,
117+
structure,
113118
addClassToHast,
114119
get source() {
115120
return transformerContext.source
@@ -121,7 +126,7 @@ export function tokensToHast(
121126
return options
122127
},
123128
get root() {
124-
return tree
129+
return root
125130
},
126131
get pre() {
127132
return preNode
@@ -135,8 +140,12 @@ export function tokensToHast(
135140
}
136141

137142
tokens.forEach((line, idx) => {
138-
if (idx)
139-
lines.push({ type: 'text', value: '\n' })
143+
if (idx) {
144+
if (structure === 'inline')
145+
root.children.push({ type: 'element', tagName: 'br', properties: {}, children: [] })
146+
else if (structure === 'classic')
147+
lines.push({ type: 'text', value: '\n' })
148+
}
140149

141150
let lineNode: Element = {
142151
type: 'element',
@@ -162,28 +171,35 @@ export function tokensToHast(
162171
for (const transformer of transformers)
163172
tokenNode = transformer?.span?.call(context, tokenNode, idx + 1, col, lineNode) || tokenNode
164173

165-
lineNode.children.push(tokenNode)
174+
if (structure === 'inline')
175+
root.children.push(tokenNode)
176+
else if (structure === 'classic')
177+
lineNode.children.push(tokenNode)
166178
col += token.content.length
167179
}
168180

169-
for (const transformer of transformers)
170-
lineNode = transformer?.line?.call(context, lineNode, idx + 1) || lineNode
181+
if (structure === 'classic') {
182+
for (const transformer of transformers)
183+
lineNode = transformer?.line?.call(context, lineNode, idx + 1) || lineNode
171184

172-
lineNodes.push(lineNode)
173-
lines.push(lineNode)
185+
lineNodes.push(lineNode)
186+
lines.push(lineNode)
187+
}
174188
})
175189

176-
for (const transformer of transformers)
177-
codeNode = transformer?.code?.call(context, codeNode) || codeNode
190+
if (structure === 'classic') {
191+
for (const transformer of transformers)
192+
codeNode = transformer?.code?.call(context, codeNode) || codeNode
178193

179-
preNode.children.push(codeNode)
194+
preNode.children.push(codeNode)
180195

181-
for (const transformer of transformers)
182-
preNode = transformer?.pre?.call(context, preNode) || preNode
196+
for (const transformer of transformers)
197+
preNode = transformer?.pre?.call(context, preNode) || preNode
183198

184-
tree.children.push(preNode)
199+
root.children.push(preNode)
200+
}
185201

186-
let result = tree
202+
let result = root
187203
for (const transformer of transformers)
188204
result = transformer?.root?.call(context, result) || result
189205

packages/core/src/types/options.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,16 @@ export interface CodeToHastOptionsCommon<Languages extends string = string>
132132
* @default true
133133
*/
134134
mergeWhitespaces?: boolean | 'never'
135+
136+
/**
137+
* The structure of the generated HAST and HTML.
138+
*
139+
* - `classic`: The classic structure with `<pre>` and `<code>` elements, each line wrapped with a `<span class="line">` element.
140+
* - `inline`: All tokens are rendered as `<span>`, line breaks are rendered as `<br>`. No `<pre>` or `<code>` elements. Default forground and background colors are not applied.
141+
*
142+
* @default 'classic'
143+
*/
144+
structure?: 'classic' | 'inline'
135145
}
136146

137147
export interface CodeOptionsMeta {

packages/core/src/types/transformers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ export interface ShikiTransformerContext extends ShikiTransformerContextSource {
3535
readonly code: Element
3636
readonly lines: Element[]
3737

38+
readonly structure: CodeToHastOptions['structure']
39+
3840
/**
3941
* Utility to append class to a hast node
4042
*

packages/shiki/test/hast.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,22 @@ describe('should', () => {
2222
<span class="line"><span style="color:#B07D48">foo</span><span style="color:#999999">.</span><span style="color:#B07D48">bar</span></span></code></pre>"
2323
`)
2424
})
25+
26+
it('structure inline', async () => {
27+
const shiki = await getHighlighter({
28+
themes: ['vitesse-light'],
29+
langs: ['javascript'],
30+
})
31+
32+
const hast = shiki.codeToHast('console.log\nfoo.bar', {
33+
lang: 'js',
34+
theme: 'vitesse-light',
35+
structure: 'inline',
36+
})
37+
38+
expect(toHtml(hast))
39+
.toMatchInlineSnapshot(`"<span style="color:#B07D48">console</span><span style="color:#999999">.</span><span style="color:#B07D48">log</span><br><span style="color:#B07D48">foo</span><span style="color:#999999">.</span><span style="color:#B07D48">bar</span>"`)
40+
})
2541
})
2642

2743
it('hasfocus support', async () => {

packages/twoslash/src/renderer-rich.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -223,16 +223,22 @@ export function rendererRich(options: RendererRichOptions = {}): TwoslashRendere
223223

224224
const popupContents: ElementContent[] = []
225225

226-
const typeCode = ((this.codeToHast(
227-
content,
228-
{
229-
...this.options,
230-
transformers: [],
231-
lang: (this.options.lang === 'tsx' || this.options.lang === 'jsx')
232-
? 'tsx'
233-
: 'ts',
234-
},
235-
).children[0] as Element).children as Element[])[0]
226+
const typeCode: Element = {
227+
type: 'element',
228+
tagName: 'code',
229+
properties: {},
230+
children: this.codeToHast(
231+
content,
232+
{
233+
...this.options,
234+
transformers: [],
235+
lang: (this.options.lang === 'tsx' || this.options.lang === 'jsx')
236+
? 'tsx'
237+
: 'ts',
238+
structure: 'inline',
239+
},
240+
).children as ElementContent[],
241+
}
236242
typeCode.properties.class = 'twoslash-popup-code'
237243

238244
popupContents.push(

packages/twoslash/test/out/completion-end-multifile-2.ts.html

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)