Skip to content

Commit

Permalink
feat: (试验性)代码高亮支持 twoslash
Browse files Browse the repository at this point in the history
  • Loading branch information
pengzhanbo committed Jan 11, 2024
1 parent de8d9e0 commit d0fdf79
Show file tree
Hide file tree
Showing 12 changed files with 475 additions and 84 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"taze",
"Tongji",
"tsbuildinfo",
"twoslash",
"vite",
"vuepress",
"vueuse",
Expand Down
50 changes: 50 additions & 0 deletions docs/2.preview/主题效果预览.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,56 @@ const obj = {
}
```

**Code Blocks TwoSlash**

```ts twoslash
// @errors: 2339
const welcome = "Tudo bem gente?"
const words = welcome.contains(" ")
```

```ts twoslash
import express from "express"
const app = express()
app.get("/", function (req, res) {
res.send
})
app.listen(3000)
```

```ts twoslash
import { getHighlighterCore } from 'shikiji/core'

const highlighter = await getHighlighterCore({})
// @log: Custom log message
const a = 1
// @error: Custom error message
const b = 1
// @warn: Custom warning message
const c = 1
// @annotate: Custom annotation message
```

```ts twoslash
// @errors: 2540
interface Todo {
title: string
}

const todo: Readonly<Todo> = {
title: 'Delete inactive users'.toUpperCase(),
// ^?
}

todo.title = 'Hello'

Number.parseInt('123', 10)
// ^|

//
//
```

**代码分组**

::: code-tabs
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@
"typescript": "^5.3.3",
"vite": "^5.0.11"
},
"pnpm": {
"patchedDependencies": {
"@vuepress/markdown@2.0.0-rc.0": "patches/@vuepress__markdown@2.0.0-rc.0.patch"
}
},
"lint-staged": {
"*": "eslint --fix"
},
Expand Down
13 changes: 13 additions & 0 deletions patches/@vuepress__markdown@2.0.0-rc.0.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/dist/index.js b/dist/index.js
index 996b0d16dac39667cc25496e52adcc9dd2b2befa..a4c9f5ba3a20967d9a561fcc73178d9e84f48279 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -245,7 +245,7 @@ var codePlugin = (md, {
const info = token.info ? md.utils.unescapeAll(token.info).trim() : "";
const language = resolveLanguage(info);
const languageClass = `${options.langPrefix}${language.name}`;
- const code = options.highlight?.(token.content, language.name, "") || md.utils.escapeHtml(token.content);
+ const code = options.highlight?.(token.content, language.name, info || "") || md.utils.escapeHtml(token.content);
token.attrJoin("class", languageClass);
let result = code.startsWith("<pre") ? code : `<pre${slf.renderAttrs(token)}><code>${code}</code></pre>`;
const useVPre = resolveVPre(info) ?? vPreBlock;
2 changes: 1 addition & 1 deletion plugins/plugin-copy-code/src/client/setupCopyCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const options = __COPY_CODE_OPTIONS__
const RE_LANGUAGE = /language-([\w]+)/
const RE_START_CODE = /^ *(\$|>)/gm
const shells = ['shellscript', 'shell', 'bash', 'sh', 'zsh']
const ignoredNodes = ['.diff.remove']
const ignoredNodes = ['.diff.remove', '.vp-copy-ignore']

function isMobile(): boolean {
return navigator
Expand Down
3 changes: 2 additions & 1 deletion plugins/plugin-shikiji/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
"nanoid": "^5.0.4",
"picocolors": "^1.0.0",
"shikiji": "^0.9.18",
"shikiji-transformers": "^0.9.18"
"shikiji-transformers": "^0.9.18",
"shikiji-twoslash": "^0.9.18"
},
"publishConfig": {
"access": "public"
Expand Down
89 changes: 66 additions & 23 deletions plugins/plugin-shikiji/src/node/highlight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import {
transformerNotationFocus,
transformerNotationHighlight,
} from 'shikiji-transformers'
import { rendererRich, transformerTwoSlash } from 'shikiji-twoslash'
import type { HighlighterOptions, ThemeOptions } from './types.js'
import { resolveAttrs } from './resolveAttrs.js'

const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10)

Expand Down Expand Up @@ -88,9 +90,7 @@ export async function highlight(
lang = defaultLang
}
}

// const lineOptions = attrsToLines(attrs)

const { attrs: attributes, rawAttrs } = resolveAttrs(attrs || '')
const mustaches = new Map<string, string>()

const removeMustache = (s: string) => {
Expand All @@ -110,32 +110,75 @@ export async function highlight(
mustaches.forEach((marker, match) => {
s = s.replaceAll(marker, match)
})
return s
}

const fillEmptyHighlightedLine = (s: string) => {
return `${s.replace(
/(<span class="line highlighted">)(<\/span>)/g,
'$1<wbr>$2',
).replace(/(\/\/\s*?\[)\\(!code.*?\])/g, '$1$2')}\n`
return `${s}\n`
}

str = removeMustache(str).trimEnd()

const highlighted = highlighter.codeToHtml(str, {
lang,
transformers: [
...transformers,
...userTransformers,
],
meta: {
__raw: attrs,
const inlineTransformers: ShikijiTransformer[] = [
{
name: 'vuepress-shikiji:empty-line',
pre(hast) {
hast.children.forEach((code) => {
if (code.type === 'element' && code.tagName === 'code') {
code.children.forEach((span) => {
if (
span.type === 'element'
&& span.tagName === 'span'
&& Array.isArray(span.properties.class)
&& span.properties.class.includes('line')
&& span.children.length === 0
) {
span.children.push({
type: 'element',
tagName: 'wbr',
properties: {},
children: [],
})
}
})
}
})
},
},
...(typeof theme === 'object' && 'light' in theme && 'dark' in theme
? { themes: theme, defaultColor: false }
: { theme }),
})
{
name: 'vuepress-shikiji:remove-escape',
postprocess(code) {
return code.replace(/\[\\\!code/g, '[!code')
},
},
]

if (attributes.twoslash) {
inlineTransformers.push(transformerTwoSlash({
renderer: rendererRich({
classExtra: 'vp-copy-ignore',
}),
}))
}

return fillEmptyHighlightedLine(restoreMustache(highlighted))
try {
const highlighted = highlighter.codeToHtml(str, {
lang,
transformers: [
...transformers,
...inlineTransformers,
...userTransformers,
],
meta: {
__raw: rawAttrs,
},
...(typeof theme === 'object' && 'light' in theme && 'dark' in theme
? { themes: theme, defaultColor: false }
: { theme }),
})

return restoreMustache(highlighted)
}
catch (e) {
logger.error(e)
return str
}
}
}
43 changes: 43 additions & 0 deletions plugins/plugin-shikiji/src/node/resolveAttrs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const RE_ATTR_VALUE = /(?:^|\s+)(?<attr>[\w\d-]+)(?:=\s*(?<quote>['"])(?<value>.+?)\k<quote>)?(?:\s+|$)/
const RE_CODE_BLOCKS = /^[\w\d-]*(\s*:[\w\d-]*)?(\s*\{[\d\w-,\s]+\})?\s*/

export function resolveAttrs(info: string): {
attrs: Record<string, string | boolean>
rawAttrs: string
} {
if (!info)
return { rawAttrs: '', attrs: {} }
info = info.replace(RE_CODE_BLOCKS, '').trim()
if (!info)
return { rawAttrs: '', attrs: {} }

const attrs: Record<string, string | boolean> = {}
const rawAttrs = info

let matched: RegExpMatchArray | null

// eslint-disable-next-line no-cond-assign
while (matched = info.match(RE_ATTR_VALUE)) {
const { attr, value } = matched.groups || {}
attrs[attr] = value ?? true
info = info.slice(matched[0].length)
}

Object.keys(attrs).forEach((key) => {
let value = attrs[key]
value = typeof value === 'string' ? value.trim() : value
if (value === 'true')
value = true
else if (value === 'false')
value = false

attrs[key] = value

if (key.includes('-')) {
const _key = key.replace(/-(\w)/g, (_, c) => c.toUpperCase())
attrs[_key] = value
}
})

return { attrs, rawAttrs }
}
3 changes: 0 additions & 3 deletions plugins/plugin-shikiji/src/node/shikijiPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ import type { Plugin } from '@vuepress/core'
import { highlight } from './highlight.js'
import type { HighlighterOptions } from './types'

/**
* Options of @vuepress/plugin-shiki
*/
export type ShikijiPluginOptions = HighlighterOptions

export function shikijiPlugin(options: ShikijiPluginOptions = {}): Plugin {
Expand Down
Loading

0 comments on commit d0fdf79

Please sign in to comment.