diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..6313b56c5
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text=auto eol=lf
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index ac25b5035..143249597 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -58,7 +58,7 @@ export default defineConfig({
processHoverInfo(info) {
return defaultHoverInfoProcessor(info)
// Remove shikiji_core namespace
- .replace(/\bshikiji_core\./g, '')
+ .replace(/shikiji_core\./g, '')
// Remove member access
.replace(/^[a-zA-Z0-9_]*(\<[^\>]*\>)?\./, '')
},
diff --git a/docs/packages/transformers.md b/docs/packages/transformers.md
index e50c783fc..0ffa5d25c 100644
--- a/docs/packages/transformers.md
+++ b/docs/packages/transformers.md
@@ -1,3 +1,7 @@
+---
+outline: deep
+---
+
# shikiji-transformers
diff --git a/docs/packages/twoslash.md b/docs/packages/twoslash.md
index 7ecb73e18..bf1c8bd48 100644
--- a/docs/packages/twoslash.md
+++ b/docs/packages/twoslash.md
@@ -2,7 +2,7 @@
-A Shikiji transformer for [TypeScript TwoSlash](https://www.typescriptlang.org/dev/twoslash/), provide inline type hover inside code blocks. Inspired by [`shiki-twoslash`](https://shikijs.github.io/twoslash/).
+A Shikiji transformer for [`twoslash`](https://github.com/twoslashes/twoslash), provide inline type hover inside code blocks. Inspired by [`shiki-twoslash`](https://shikijs.github.io/twoslash/).
## Install
@@ -44,7 +44,7 @@ We provide two renderers built-in, and you can also create your own:
[Source code](https://github.com/antfu/shikiji/blob/main/packages/shikiji-twoslash/src/renderer-classic.ts)
-This is the default renderer that aligns with the output of [`shiki-twoslash`](https://shikijs.github.io/twoslash/).
+This renderer aligns with the output of [`shiki-twoslash`](https://shikijs.github.io/twoslash/).
You might need to reference `shiki-twoslash`'s CSS to make it look good. [Here](https://github.com/antfu/shikiji/blob/main/packages/shikiji-twoslash/style-classic.css) we also copied the CSS from `shiki-twoslash` but it might need some cleanup.
@@ -52,7 +52,8 @@ You might need to reference `shiki-twoslash`'s CSS to make it look good. [Here](
[Source code](https://github.com/antfu/shikiji/blob/main/packages/shikiji-twoslash/src/renderer-rich.ts)
-This renderer provides a more explicit class name that is always prefixed with `twoslash-` for better scoping. In addition, it runs syntax highlighting on the hover information.
+This renderer provides a more explicit class name is prefixed with `twoslash-` for better scoping.
+In addition, it runs syntax highlighting on the hover information.
```ts twoslash
import { rendererRich, transformerTwoSlash } from 'shikiji-twoslash'
diff --git a/docs/packages/vitepress.md b/docs/packages/vitepress.md
index 46d772cce..a4ce81f80 100644
--- a/docs/packages/vitepress.md
+++ b/docs/packages/vitepress.md
@@ -1,3 +1,7 @@
+---
+outline: deep
+---
+
# VitePress Integration
[VitePress](https://vitepress.dev/) uses Shikiji under the hood, so you don't need explicit integration.
@@ -10,6 +14,8 @@ To enable [TypeScript TwoSlash](/packages/twoslash) (type hover on code snippets
+### Setup
+
```bash
npm i -D vitepress-plugin-twoslash
```
@@ -78,3 +84,35 @@ It will be rendered as:
console.log('hello')
// ^?
```
+
+
+
+### Vue Single File Component
+
+In addition, this plugin also integrated [`twoslash-vue`](https://github.com/antfu/twoslash-vue) for you, so that you can also highlight Vue SFC blocks with `vue twoslash`:
+
+```vue twoslash
+
+
+
+
+
+```
diff --git a/packages/shikiji-twoslash/package.json b/packages/shikiji-twoslash/package.json
index e4f4b04ed..3201c88c0 100644
--- a/packages/shikiji-twoslash/package.json
+++ b/packages/shikiji-twoslash/package.json
@@ -55,8 +55,8 @@
"test": "vitest"
},
"dependencies": {
- "@typescript/twoslash": "^3.2.4",
- "shikiji-core": "workspace:*"
+ "shikiji-core": "workspace:*",
+ "twoslash": "^0.0.6"
},
"devDependencies": {
"@iconify-json/carbon": "^1.1.27",
diff --git a/packages/shikiji-twoslash/scripts/icons.ts b/packages/shikiji-twoslash/scripts/icons.ts
index f3096529b..70076cbc1 100644
--- a/packages/shikiji-twoslash/scripts/icons.ts
+++ b/packages/shikiji-twoslash/scripts/icons.ts
@@ -2,9 +2,7 @@ import fs from 'node:fs/promises'
import { icons as codicon } from '@iconify-json/codicon'
import { icons as carbon } from '@iconify-json/carbon'
import { fromHtml } from 'hast-util-from-html'
-import type { TwoSlashReturn } from '@typescript/twoslash'
-
-type CompletionItem = NonNullable[0]
+import type { CompletionItem } from '../src/icons'
async function buildIcons(filepath: string, map: Record) {
const result = Object.fromEntries(
diff --git a/packages/shikiji-twoslash/src/core.ts b/packages/shikiji-twoslash/src/core.ts
index c8dccd9a7..7538796e7 100644
--- a/packages/shikiji-twoslash/src/core.ts
+++ b/packages/shikiji-twoslash/src/core.ts
@@ -2,31 +2,32 @@
* This file is the core of the shikiji-twoslash package,
* Decoupled from twoslash's implementation and allowing to introduce custom implementation or cache system.
*/
-import type { twoslasher } from '@typescript/twoslash'
+import type { TwoSlashExecuteOptions, TwoSlashReturn } from 'twoslash'
import type { ShikijiTransformer } from 'shikiji-core'
import type { Element, ElementContent, Text } from 'hast'
import type { ModuleKind, ScriptTarget } from 'typescript'
import { addClassToHast } from 'shikiji-core'
-import { rendererClassic } from './renderer-classic'
-import type { TransformerTwoSlashOptions } from './types'
+import type { TransformerTwoSlashOptions, TwoSlashRenderer } from './types'
export * from './types'
-export * from './renderer-classic'
export * from './renderer-rich'
+export * from './renderer-classic'
export * from './icons'
-export function defaultTwoSlashOptions() {
+export function defaultTwoSlashOptions(): TwoSlashExecuteOptions {
return {
customTags: ['annotate', 'log', 'warn', 'error'],
- defaultCompilerOptions: {
+ compilerOptions: {
module: 99 satisfies ModuleKind.ESNext,
target: 99 satisfies ScriptTarget.ESNext,
},
}
}
-export function createTransformerFactory(defaultTwoslasher: typeof twoslasher) {
+type TwoSlashFunction = (code: string, lang?: string, options?: TwoSlashExecuteOptions) => TwoSlashReturn
+
+export function createTransformerFactory(defaultTwoslasher: TwoSlashFunction, defaultRenderer?: TwoSlashRenderer) {
return function transformerTwoSlash(options: TransformerTwoSlashOptions = {}): ShikijiTransformer {
const {
langs = ['ts', 'tsx'],
@@ -38,9 +39,13 @@ export function createTransformerFactory(defaultTwoslasher: typeof twoslasher) {
},
twoslasher = defaultTwoslasher,
explicitTrigger = false,
- renderer = rendererClassic(),
+ renderer = defaultRenderer,
throws = true,
} = options
+
+ if (!renderer)
+ throw new Error('[shikiji-twoslash] Missing renderer')
+
const filter = options.filter || ((lang, _, options) => langs.includes(lang) && (!explicitTrigger || /\btwoslash\b/.test(options.meta?.__raw || '')))
return {
preprocess(code, shikijiOptions) {
@@ -115,8 +120,6 @@ export function createTransformerFactory(defaultTwoslasher: typeof twoslasher) {
const skipTokens = new Set()
for (const error of twoslash.errors) {
- if (error.line == null || error.character == null)
- return
const token = locateTextToken(error.line, error.character)
if (!token)
continue
@@ -133,39 +136,38 @@ export function createTransformerFactory(defaultTwoslasher: typeof twoslasher) {
}
for (const query of twoslash.queries) {
- if (query.kind === 'completions') {
- const token = locateTextToken(query.line - 1, query.offset)
- if (!token)
- continue
-
- skipTokens.add(token)
+ const token = locateTextToken(query.line, query.character)
+ if (!token)
+ continue
- if (renderer.nodeCompletions) {
- const clone = { ...token }
- Object.assign(token, renderer.nodeCompletions.call(this, query, clone))
- }
+ skipTokens.add(token)
- if (renderer.lineCompletions)
- insertAfterLine(query.line, renderer.lineCompletions.call(this, query))
+ if (renderer.nodeQuery) {
+ const clone = { ...token }
+ Object.assign(token, renderer.nodeQuery.call(this, query, clone))
}
- else if (query.kind === 'query') {
- const token = locateTextToken(query.line - 1, query.offset)
- if (!token)
- continue
- skipTokens.add(token)
+ if (renderer.lineQuery)
+ insertAfterLine(query.line, renderer.lineQuery.call(this, query, token))
+ }
- if (renderer.nodeQuery) {
- const clone = { ...token }
- Object.assign(token, renderer.nodeQuery.call(this, query, clone))
- }
+ for (const completion of twoslash.completions) {
+ const token = locateTextToken(completion.line, completion.character)
+ if (!token)
+ continue
+
+ skipTokens.add(token)
- if (renderer.lineQuery)
- insertAfterLine(query.line, renderer.lineQuery.call(this, query, token))
+ if (renderer.nodeCompletions) {
+ const clone = { ...token }
+ Object.assign(token, renderer.nodeCompletions.call(this, completion, clone))
}
+
+ if (renderer.lineCompletions)
+ insertAfterLine(completion.line, renderer.lineCompletions.call(this, completion))
}
- for (const info of twoslash.staticQuickInfos) {
+ for (const info of twoslash.hovers) {
const token = locateTextToken(info.line, info.character)
if (!token || token.type !== 'text')
continue
diff --git a/packages/shikiji-twoslash/src/icons.ts b/packages/shikiji-twoslash/src/icons.ts
index a7684029b..8ebad22f3 100644
--- a/packages/shikiji-twoslash/src/icons.ts
+++ b/packages/shikiji-twoslash/src/icons.ts
@@ -1,9 +1,9 @@
import type { Element } from 'hast'
-import type { TwoSlashReturn } from '@typescript/twoslash'
+import type { NodeCompletion } from 'twoslash'
import completionIcons from './icons-completions.json'
import tagIcons from './icons-tags.json'
-export type CompletionItem = NonNullable[0]
+export type CompletionItem = NonNullable[number]
export const defaultCompletionIcons: Record = completionIcons as any
export const defaultCustomTagIcons: Record = tagIcons as any
diff --git a/packages/shikiji-twoslash/src/index.ts b/packages/shikiji-twoslash/src/index.ts
index aa9f9bc4a..86f9f01f4 100644
--- a/packages/shikiji-twoslash/src/index.ts
+++ b/packages/shikiji-twoslash/src/index.ts
@@ -1,9 +1,12 @@
-import { twoslasher } from '@typescript/twoslash'
-import { createTransformerFactory } from './core'
+import { createTwoSlasher } from 'twoslash'
+import { createTransformerFactory, rendererClassic } from './core'
export * from './core'
/**
* Factory function to create a Shikiji transformer for twoslash integrations.
*/
-export const transformerTwoSlash = createTransformerFactory(twoslasher)
+export const transformerTwoSlash = /* @__PURE__ */ createTransformerFactory(
+ /* @__PURE__ */ createTwoSlasher(),
+ /* @__PURE__ */ rendererClassic(),
+)
diff --git a/packages/shikiji-twoslash/src/renderer-classic.ts b/packages/shikiji-twoslash/src/renderer-classic.ts
index de84113ae..17fcbd8c2 100644
--- a/packages/shikiji-twoslash/src/renderer-classic.ts
+++ b/packages/shikiji-twoslash/src/renderer-classic.ts
@@ -1,9 +1,9 @@
-import type { TwoSlashRenderers } from './types'
+import type { TwoSlashRenderer } from './types'
/**
* The default renderer aligning with the original `shiki-twoslash` output.
*/
-export function rendererClassic(): TwoSlashRenderers {
+export function rendererClassic(): TwoSlashRenderer {
return {
nodeStaticInfo(info, node) {
return {
@@ -41,7 +41,7 @@ export function rendererClassic(): TwoSlashRenderers {
children: [
{
type: 'text',
- value: error.renderedMessage,
+ value: error.text,
},
],
},
@@ -69,7 +69,7 @@ export function rendererClassic(): TwoSlashRenderers {
children: [
{
type: 'text',
- value: error.renderedMessage,
+ value: error.text,
},
],
},
@@ -83,7 +83,7 @@ export function rendererClassic(): TwoSlashRenderers {
tagName: 'div',
properties: { class: 'meta-line' },
children: [
- { type: 'text', value: ' '.repeat(query.offset) },
+ { type: 'text', value: ' '.repeat(query.character) },
{
type: 'element',
tagName: 'span',
@@ -134,7 +134,7 @@ export function rendererClassic(): TwoSlashRenderers {
lineQuery(query, targetNode) {
const targetText = targetNode?.type === 'text' ? targetNode.value : ''
- const offset = Math.max(0, (query.offset || 0) + Math.floor(targetText.length / 2) - 1)
+ const offset = Math.max(0, (query.character || 0) + Math.floor(targetText.length / 2) - 1)
return [
{
@@ -179,7 +179,7 @@ export function rendererClassic(): TwoSlashRenderers {
children: [
{
type: 'text',
- value: tag.annotation || '',
+ value: tag.text || '',
},
],
},
diff --git a/packages/shikiji-twoslash/src/renderer-rich.ts b/packages/shikiji-twoslash/src/renderer-rich.ts
index 6c38df25e..311020e8d 100644
--- a/packages/shikiji-twoslash/src/renderer-rich.ts
+++ b/packages/shikiji-twoslash/src/renderer-rich.ts
@@ -1,6 +1,6 @@
import type { Element, ElementContent } from 'hast'
import type { ShikijiTransformerContextCommon } from 'shikiji-core'
-import type { TwoSlashRenderers } from './types'
+import type { TwoSlashRenderer } from './types'
import type { CompletionItem } from './icons'
import { defaultCompletionIcons, defaultCustomTagIcons } from './icons'
@@ -65,7 +65,7 @@ export interface RendererRichOptions {
* An alternative renderer that providers better prefixed class names,
* with syntax highlight for the info text.
*/
-export function rendererRich(options: RendererRichOptions = {}): TwoSlashRenderers {
+export function rendererRich(options: RendererRichOptions = {}): TwoSlashRenderer {
const {
completionIcons = defaultCompletionIcons,
customTagIcons = defaultCustomTagIcons,
@@ -85,7 +85,7 @@ export function rendererRich(options: RendererRichOptions = {}): TwoSlashRendere
return []
const text = processHoverInfo(info.text) ?? info.text
- if (!text)
+ if (!text.trim())
return []
const themedContent = ((codeToHast(text, {
@@ -118,6 +118,9 @@ export function rendererRich(options: RendererRichOptions = {}): TwoSlashRendere
nodeStaticInfo(info, node) {
const themedContent = hightlightPopupContent(this.codeToHast, this.options, info)
+ if (!themedContent.length)
+ return node
+
return {
type: 'element',
tagName: 'span',
@@ -282,7 +285,7 @@ export function rendererRich(options: RendererRichOptions = {}): TwoSlashRendere
children: [
{
type: 'text',
- value: error.renderedMessage,
+ value: error.text,
},
],
},
@@ -312,7 +315,7 @@ export function rendererRich(options: RendererRichOptions = {}): TwoSlashRendere
: [],
{
type: 'text',
- value: tag.annotation || '',
+ value: tag.text || '',
},
],
},
diff --git a/packages/shikiji-twoslash/src/types.ts b/packages/shikiji-twoslash/src/types.ts
index d1ebca663..1ad131215 100644
--- a/packages/shikiji-twoslash/src/types.ts
+++ b/packages/shikiji-twoslash/src/types.ts
@@ -1,4 +1,4 @@
-import type { TwoSlashOptions, TwoSlashReturn, twoslasher } from '@typescript/twoslash'
+import type { NodeCompletion, NodeError, NodeHover, NodeQuery, NodeTag, TwoSlashOptions, TwoSlashReturn, twoslasher } from 'twoslash'
import type { CodeToHastOptions, ShikijiTransformerContext } from 'shikiji-core'
import type { Element, ElementContent, Text } from 'hast'
@@ -39,7 +39,7 @@ export interface TransformerTwoSlashOptions {
/**
* Custom renderers to decide how each info should be rendered
*/
- renderer?: TwoSlashRenderers
+ renderer?: TwoSlashRenderer
/**
* Strictly throw when there is an error
* @default true
@@ -47,14 +47,14 @@ export interface TransformerTwoSlashOptions {
throws?: boolean
}
-export interface TwoSlashRenderers {
- lineError?(this: ShikijiTransformerContext, error: TwoSlashReturn['errors'][0]): ElementContent[]
- lineCustomTag?(this: ShikijiTransformerContext, tag: TwoSlashReturn['tags'][0]): ElementContent[]
- lineQuery?(this: ShikijiTransformerContext, query: TwoSlashReturn['queries'][0], targetNode?: Element | Text): ElementContent[]
- lineCompletions?(this: ShikijiTransformerContext, query: TwoSlashReturn['queries'][0]): ElementContent[]
+export interface TwoSlashRenderer {
+ lineError?(this: ShikijiTransformerContext, error: NodeError): ElementContent[]
+ lineCustomTag?(this: ShikijiTransformerContext, tag: NodeTag): ElementContent[]
+ lineQuery?(this: ShikijiTransformerContext, query: NodeQuery, targetNode?: Element | Text): ElementContent[]
+ lineCompletions?(this: ShikijiTransformerContext, query: NodeCompletion): ElementContent[]
- nodeError?(this: ShikijiTransformerContext, error: TwoSlashReturn['errors'][0], node: Element | Text): Partial
- nodeStaticInfo(this: ShikijiTransformerContext, info: TwoSlashReturn['staticQuickInfos'][0], node: Element | Text): Partial
- nodeQuery?(this: ShikijiTransformerContext, query: TwoSlashReturn['queries'][0], node: Element | Text): Partial
- nodeCompletions?(this: ShikijiTransformerContext, query: TwoSlashReturn['queries'][0], node: Element | Text): Partial
+ nodeError?(this: ShikijiTransformerContext, error: NodeError, node: Element | Text): Partial
+ nodeStaticInfo(this: ShikijiTransformerContext, info: NodeHover, node: Element | Text): Partial
+ nodeQuery?(this: ShikijiTransformerContext, query: NodeQuery, node: Element | Text): Partial
+ nodeCompletions?(this: ShikijiTransformerContext, query: NodeCompletion, node: Element | Text): Partial
}
diff --git a/packages/shikiji-twoslash/test/out/classic/completions.html b/packages/shikiji-twoslash/test/out/classic/completions.html
index 7eadad1bd..743c282f5 100644
--- a/packages/shikiji-twoslash/test/out/classic/completions.html
+++ b/packages/shikiji-twoslash/test/out/classic/completions.html
@@ -4,4 +4,4 @@
html, body { margin: 0; }
.shiki { padding: 2em; }
-
consta=Number.isNaN(123)
isFinite
isInteger
isNaN
isSafeInteger
\ No newline at end of file
+
consta=Number.isNaN(123)
isFinite
isInteger
isNaN
isSafeInteger
\ No newline at end of file
diff --git a/packages/shikiji-twoslash/test/out/classic/console_log.html b/packages/shikiji-twoslash/test/out/classic/console_log.html
index b48616980..eef051bd5 100644
--- a/packages/shikiji-twoslash/test/out/classic/console_log.html
+++ b/packages/shikiji-twoslash/test/out/classic/console_log.html
@@ -5,4 +5,5 @@
.shiki { padding: 2em; }
// Hello
-console.error("This is an error")
This is an error
\ No newline at end of file
+console.error("This is an error")
+
This is an error
\ No newline at end of file
diff --git a/packages/shikiji-twoslash/test/out/classic/cuts_out_unnecessary_code.html b/packages/shikiji-twoslash/test/out/classic/cuts_out_unnecessary_code.html
index 20a60c1ba..419fc8833 100644
--- a/packages/shikiji-twoslash/test/out/classic/cuts_out_unnecessary_code.html
+++ b/packages/shikiji-twoslash/test/out/classic/cuts_out_unnecessary_code.html
@@ -8,6 +8,6 @@
throw"unimplemented"}
-leta=createLabel("typescript")
-
let a: NameLabel
letb=createLabel(2.8)
-
function createLabel<2.8>(idOrName: 2.8): IdLabel
letc=createLabel(Math.random()?"hello":42)
random
round
\ No newline at end of file
+leta=createLabel("typescript")
let a: NameLabel
+letb=createLabel(2.8)
function createLabel<2.8>(idOrName: 2.8): IdLabel
+letc=createLabel(Math.random()?"hello":42)
random
round
\ No newline at end of file
diff --git a/packages/shikiji-twoslash/test/out/rich/custom-tags.html b/packages/shikiji-twoslash/test/out/rich/custom-tags.html
index fc6aff0f0..ecc24874d 100644
--- a/packages/shikiji-twoslash/test/out/rich/custom-tags.html
+++ b/packages/shikiji-twoslash/test/out/rich/custom-tags.html
@@ -29,7 +29,7 @@
constconstshiki:HighlighterCoreshiki=awaitfunctiongetHighlighterCore(options?:HighlighterCoreOptions|undefined):Promise<HighlighterCore>
Create a Shikiji core highlighter instance, with no languages or themes bundled.
Wasm and each language and theme must be loaded manually.