Skip to content

Commit aea2511

Browse files
hddhyqantfu
andauthored
feat(twoslash): add line query rendering option for twoslash renderer (#695)
Co-authored-by: Anthony Fu <github@antfu.me>
1 parent 107c9db commit aea2511

File tree

4 files changed

+159
-1
lines changed

4 files changed

+159
-1
lines changed

packages/twoslash/src/renderer-rich.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ export interface RendererRichOptions {
8181
*/
8282
renderMarkdownInline?: (this: ShikiTransformerContextCommon, markdown: string, context: string) => ElementContent[]
8383

84+
/**
85+
* The way query should be rendered.
86+
* - `'popup'`: Render the query in the absolute popup
87+
* - `'line'`: Render the query line after the line of code
88+
* @default 'popup'
89+
*/
90+
queryRendering?: 'popup' | 'line'
91+
8492
/**
8593
* Extensions for the genreated HAST tree.
8694
*/
@@ -206,6 +214,7 @@ export function rendererRich(options: RendererRichOptions = {}): TwoslashRendere
206214
classExtra = '',
207215
jsdoc = true,
208216
errorRendering = 'line',
217+
queryRendering = 'popup',
209218
renderMarkdown = renderMarkdownPassThrough,
210219
renderMarkdownInline = renderMarkdownPassThrough,
211220
hast,
@@ -354,6 +363,22 @@ export function rendererRich(options: RendererRichOptions = {}): TwoslashRendere
354363

355364
const themedContent = highlightPopupContent.call(this, query)
356365

366+
if (queryRendering !== 'popup') {
367+
return extend(
368+
hast?.queryToken,
369+
{
370+
type: 'element',
371+
tagName: 'span',
372+
properties: {
373+
class: 'twoslash-hover',
374+
},
375+
children: [
376+
node,
377+
],
378+
},
379+
)
380+
}
381+
357382
const popup = extend(
358383
hast?.queryPopup,
359384
{
@@ -567,6 +592,45 @@ export function rendererRich(options: RendererRichOptions = {}): TwoslashRendere
567592
]
568593
},
569594

595+
lineQuery(query, node) {
596+
if (queryRendering !== 'line')
597+
return []
598+
599+
const themedContent = highlightPopupContent.call(this, query)
600+
const targetNode = node?.type === 'element' ? node.children[0] : undefined
601+
const targetText = targetNode?.type === 'text' ? targetNode.value : ''
602+
const offset = Math.max(0, (query.character || 0) + Math.floor(targetText.length / 2) - 2)
603+
604+
return [
605+
{
606+
type: 'element',
607+
tagName: 'div',
608+
properties: {
609+
class: ['twoslash-meta-line twoslash-query-line', classExtra].filter(Boolean).join(' '),
610+
},
611+
children: [
612+
{ type: 'text', value: ' '.repeat(offset) },
613+
{
614+
type: 'element',
615+
tagName: 'span',
616+
properties: {
617+
class: ['twoslash-popup-container', classExtra].filter(Boolean).join(' '),
618+
},
619+
children: [
620+
{
621+
type: 'element',
622+
tagName: 'div',
623+
properties: { class: 'twoslash-popup-arrow' },
624+
children: [],
625+
},
626+
...themedContent,
627+
],
628+
},
629+
],
630+
},
631+
]
632+
},
633+
570634
lineError(error) {
571635
if (errorRendering !== 'line')
572636
return []

packages/twoslash/style-rich.css

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@
7070

7171
.twoslash .twoslash-hover:hover .twoslash-popup-container,
7272
.twoslash .twoslash-error-hover:hover .twoslash-popup-container,
73-
.twoslash .twoslash-query-presisted .twoslash-popup-container {
73+
.twoslash .twoslash-query-presisted .twoslash-popup-container,
74+
.twoslash .twoslash-query-line .twoslash-popup-container {
7475
opacity: 1;
7576
pointer-events: auto;
7677
}
@@ -132,6 +133,13 @@
132133
font-family: var(--twoslash-code-font);
133134
}
134135

136+
/* ===== Query Line ===== */
137+
.twoslash .twoslash-query-line .twoslash-popup-container {
138+
position: relative;
139+
margin-bottom: 1.4em;
140+
transform: translateY(0.6em);
141+
}
142+
135143
/* ===== Error Line ===== */
136144
.twoslash .twoslash-error-line {
137145
position: relative;

packages/twoslash/test/out/rich/line-query.html

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

packages/twoslash/test/rich.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,41 @@ const c = 1
186186
+ colorToggle,
187187
).toMatchFileSnapshot('./out/rich/custom-tags.html')
188188
})
189+
190+
it('line-query', async () => {
191+
const code = `
192+
// @errors: 2540
193+
interface Todo {
194+
/** The title of the todo item */
195+
title: string;
196+
}
197+
198+
const todo: Readonly<Todo> = {
199+
title: "Delete inactive users".toUpperCase(),
200+
// ^?
201+
};
202+
203+
todo.title = "Hello";
204+
205+
Number.parseInt(todo.title, 10);
206+
// ^|
207+
`.trim()
208+
209+
const htmlWithSeparateLine = await codeToHtml(code, {
210+
lang: 'ts',
211+
themes: {
212+
dark: 'vitesse-dark',
213+
light: 'vitesse-light',
214+
},
215+
defaultColor: false,
216+
transformers: [
217+
transformerTwoslash({
218+
renderer: rendererRich({
219+
queryRendering: 'line',
220+
}),
221+
}),
222+
],
223+
})
224+
225+
expect(styleTag + htmlWithSeparateLine + colorToggle).toMatchFileSnapshot('./out/rich/line-query.html')
226+
})

0 commit comments

Comments
 (0)