Skip to content

Commit

Permalink
feat(twoslash): add line query rendering option for twoslash renderer (
Browse files Browse the repository at this point in the history
…#695)

Co-authored-by: Anthony Fu <github@antfu.me>
  • Loading branch information
hddhyq and antfu committed Jun 7, 2024
1 parent 107c9db commit aea2511
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 1 deletion.
64 changes: 64 additions & 0 deletions packages/twoslash/src/renderer-rich.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ export interface RendererRichOptions {
*/
renderMarkdownInline?: (this: ShikiTransformerContextCommon, markdown: string, context: string) => ElementContent[]

/**
* The way query should be rendered.
* - `'popup'`: Render the query in the absolute popup
* - `'line'`: Render the query line after the line of code
* @default 'popup'
*/
queryRendering?: 'popup' | 'line'

/**
* Extensions for the genreated HAST tree.
*/
Expand Down Expand Up @@ -206,6 +214,7 @@ export function rendererRich(options: RendererRichOptions = {}): TwoslashRendere
classExtra = '',
jsdoc = true,
errorRendering = 'line',
queryRendering = 'popup',
renderMarkdown = renderMarkdownPassThrough,
renderMarkdownInline = renderMarkdownPassThrough,
hast,
Expand Down Expand Up @@ -354,6 +363,22 @@ export function rendererRich(options: RendererRichOptions = {}): TwoslashRendere

const themedContent = highlightPopupContent.call(this, query)

if (queryRendering !== 'popup') {
return extend(
hast?.queryToken,
{
type: 'element',
tagName: 'span',
properties: {
class: 'twoslash-hover',
},
children: [
node,
],
},
)
}

const popup = extend(
hast?.queryPopup,
{
Expand Down Expand Up @@ -567,6 +592,45 @@ export function rendererRich(options: RendererRichOptions = {}): TwoslashRendere
]
},

lineQuery(query, node) {
if (queryRendering !== 'line')
return []

const themedContent = highlightPopupContent.call(this, query)
const targetNode = node?.type === 'element' ? node.children[0] : undefined
const targetText = targetNode?.type === 'text' ? targetNode.value : ''
const offset = Math.max(0, (query.character || 0) + Math.floor(targetText.length / 2) - 2)

return [
{
type: 'element',
tagName: 'div',
properties: {
class: ['twoslash-meta-line twoslash-query-line', classExtra].filter(Boolean).join(' '),
},
children: [
{ type: 'text', value: ' '.repeat(offset) },
{
type: 'element',
tagName: 'span',
properties: {
class: ['twoslash-popup-container', classExtra].filter(Boolean).join(' '),
},
children: [
{
type: 'element',
tagName: 'div',
properties: { class: 'twoslash-popup-arrow' },
children: [],
},
...themedContent,
],
},
],
},
]
},

lineError(error) {
if (errorRendering !== 'line')
return []
Expand Down
10 changes: 9 additions & 1 deletion packages/twoslash/style-rich.css
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@

.twoslash .twoslash-hover:hover .twoslash-popup-container,
.twoslash .twoslash-error-hover:hover .twoslash-popup-container,
.twoslash .twoslash-query-presisted .twoslash-popup-container {
.twoslash .twoslash-query-presisted .twoslash-popup-container,
.twoslash .twoslash-query-line .twoslash-popup-container {
opacity: 1;
pointer-events: auto;
}
Expand Down Expand Up @@ -132,6 +133,13 @@
font-family: var(--twoslash-code-font);
}

/* ===== Query Line ===== */
.twoslash .twoslash-query-line .twoslash-popup-container {
position: relative;
margin-bottom: 1.4em;
transform: translateY(0.6em);
}

/* ===== Error Line ===== */
.twoslash .twoslash-error-line {
position: relative;
Expand Down
48 changes: 48 additions & 0 deletions packages/twoslash/test/out/rich/line-query.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions packages/twoslash/test/rich.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,41 @@ const c = 1
+ colorToggle,
).toMatchFileSnapshot('./out/rich/custom-tags.html')
})

it('line-query', async () => {
const code = `
// @errors: 2540
interface Todo {
/** The title of the todo item */
title: string;
}
const todo: Readonly<Todo> = {
title: "Delete inactive users".toUpperCase(),
// ^?
};
todo.title = "Hello";
Number.parseInt(todo.title, 10);
// ^|
`.trim()

const htmlWithSeparateLine = await codeToHtml(code, {
lang: 'ts',
themes: {
dark: 'vitesse-dark',
light: 'vitesse-light',
},
defaultColor: false,
transformers: [
transformerTwoslash({
renderer: rendererRich({
queryRendering: 'line',
}),
}),
],
})

expect(styleTag + htmlWithSeparateLine + colorToggle).toMatchFileSnapshot('./out/rich/line-query.html')
})

0 comments on commit aea2511

Please sign in to comment.