feat: add plugin-shikiji
This commit is contained in:
parent
14e37ed52f
commit
1111e855cb
21
plugins/plugin-shikiji/LICENSE
Normal file
21
plugins/plugin-shikiji/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (C) 2021 - PRESENT by pengzhanbo
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
67
plugins/plugin-shikiji/README.md
Normal file
67
plugins/plugin-shikiji/README.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# `@vuepress-plume/plugin-shikiji`
|
||||||
|
|
||||||
|
使用 [`shikiji`](https://shikiji.netlify.app/) 来为 Markdown 代码块启用代码高亮。
|
||||||
|
|
||||||
|
## Install
|
||||||
|
```
|
||||||
|
yarn add @vuepress-plume/plugin-shikiji
|
||||||
|
```
|
||||||
|
## Usage
|
||||||
|
``` js
|
||||||
|
// .vuepress/config.js
|
||||||
|
const shikijiPlugin = require('@vuepress-plume/plugin-shikiji')
|
||||||
|
module.exports = {
|
||||||
|
//...
|
||||||
|
plugins: [
|
||||||
|
shikijiPlugin()
|
||||||
|
]
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface ShikijiOptions {
|
||||||
|
/**
|
||||||
|
* Custom theme for syntax highlighting.
|
||||||
|
*
|
||||||
|
* You can also pass an object with `light` and `dark` themes to support dual themes.
|
||||||
|
*
|
||||||
|
* @example { theme: 'github-dark' }
|
||||||
|
* @example { theme: { light: 'github-light', dark: 'github-dark' } }
|
||||||
|
*
|
||||||
|
* You can use an existing theme.
|
||||||
|
* @see https://github.com/antfu/shikiji/blob/main/docs/themes.md#all-themes
|
||||||
|
* Or add your own theme.
|
||||||
|
* @see https://github.com/antfu/shikiji/blob/main/docs/themes.md#load-custom-themes
|
||||||
|
*/
|
||||||
|
theme?: ThemeOptions
|
||||||
|
/**
|
||||||
|
* Languages for syntax highlighting.
|
||||||
|
* @see https://github.com/antfu/shikiji/blob/main/docs/languages.md#all-themes
|
||||||
|
*/
|
||||||
|
languages?: LanguageInput[]
|
||||||
|
/**
|
||||||
|
* Custom language aliases.
|
||||||
|
*
|
||||||
|
* @example { 'my-lang': 'js' }
|
||||||
|
* @see https://github.com/antfu/shikiji/tree/main#custom-language-aliases
|
||||||
|
*/
|
||||||
|
languageAlias?: Record<string, string>
|
||||||
|
/**
|
||||||
|
* Setup Shikiji instance
|
||||||
|
*/
|
||||||
|
shikijiSetup?: (shikiji: Highlighter) => void | Promise<void>
|
||||||
|
/**
|
||||||
|
* Fallback language when the specified language is not available.
|
||||||
|
*/
|
||||||
|
defaultHighlightLang?: string
|
||||||
|
/**
|
||||||
|
* Transformers applied to code blocks
|
||||||
|
* @see https://github.com/antfu/shikiji#hast-transformers
|
||||||
|
*/
|
||||||
|
codeTransformers?: ShikijiTransformer[]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
45
plugins/plugin-shikiji/package.json
Normal file
45
plugins/plugin-shikiji/package.json
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"name": "@vuepress-plume/plugin-shikiji",
|
||||||
|
"version": "1.0.0-beta.89",
|
||||||
|
"description": "The Plugin for VuePres 2",
|
||||||
|
"homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "pengzhanbo <volodymyr@foxmail.com>",
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": "./lib/node/index.js",
|
||||||
|
"./package.json": "./package.json"
|
||||||
|
},
|
||||||
|
"main": "lib/node/index.js",
|
||||||
|
"types": "./lib/node/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "pnpm run clean && pnpm run copy && pnpm run ts",
|
||||||
|
"clean": "rimraf lib *.tsbuildinfo",
|
||||||
|
"copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib",
|
||||||
|
"ts": "tsc -b tsconfig.build.json"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@vuepress/core": "2.0.0-rc.0",
|
||||||
|
"@vuepress/utils": "2.0.0-rc.0",
|
||||||
|
"nanoid": "^5.0.4",
|
||||||
|
"picocolors": "^1.0.0",
|
||||||
|
"shikiji": "^0.9.11",
|
||||||
|
"shikiji-transformers": "^0.9.11"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"keyword": [
|
||||||
|
"VuePress",
|
||||||
|
"vuepress plugin",
|
||||||
|
"shikiji",
|
||||||
|
"vuepress-plugin-shikiji"
|
||||||
|
]
|
||||||
|
}
|
||||||
150
plugins/plugin-shikiji/src/node/highlight.ts
Normal file
150
plugins/plugin-shikiji/src/node/highlight.ts
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import { logger } from '@vuepress/utils'
|
||||||
|
import { customAlphabet } from 'nanoid'
|
||||||
|
import c from 'picocolors'
|
||||||
|
import type { ShikijiTransformer } from 'shikiji'
|
||||||
|
import {
|
||||||
|
addClassToHast,
|
||||||
|
bundledLanguages,
|
||||||
|
getHighlighter,
|
||||||
|
isPlaintext as isPlainLang,
|
||||||
|
isSpecialLang
|
||||||
|
} from 'shikiji'
|
||||||
|
import {
|
||||||
|
transformerNotationDiff,
|
||||||
|
transformerNotationErrorLevel,
|
||||||
|
transformerNotationFocus,
|
||||||
|
transformerNotationHighlight
|
||||||
|
} from 'shikiji-transformers'
|
||||||
|
import type { HighlighterOptions, ThemeOptions } from './types.js'
|
||||||
|
|
||||||
|
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10)
|
||||||
|
|
||||||
|
export async function highlight(
|
||||||
|
theme: ThemeOptions,
|
||||||
|
options: HighlighterOptions,
|
||||||
|
): Promise<(str: string, lang: string, attrs: string) => string> {
|
||||||
|
const {
|
||||||
|
defaultHighlightLang: defaultLang = '',
|
||||||
|
codeTransformers: userTransformers = []
|
||||||
|
} = options
|
||||||
|
|
||||||
|
const highlighter = await getHighlighter({
|
||||||
|
themes:
|
||||||
|
typeof theme === 'string' || 'name' in theme
|
||||||
|
? [theme]
|
||||||
|
: [theme.light, theme.dark],
|
||||||
|
langs: [...Object.keys(bundledLanguages), ...(options.languages || [])],
|
||||||
|
langAlias: options.languageAlias
|
||||||
|
})
|
||||||
|
|
||||||
|
await options?.shikijiSetup?.(highlighter)
|
||||||
|
|
||||||
|
const transformers: ShikijiTransformer[] = [
|
||||||
|
transformerNotationDiff(),
|
||||||
|
transformerNotationFocus({
|
||||||
|
classActiveLine: 'has-focus',
|
||||||
|
classActivePre: 'has-focused-lines'
|
||||||
|
}),
|
||||||
|
transformerNotationHighlight(),
|
||||||
|
transformerNotationErrorLevel(),
|
||||||
|
{
|
||||||
|
name: 'vuepress:add-class',
|
||||||
|
pre(node) {
|
||||||
|
addClassToHast(node, 'vp-code')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vuepress:clean-up',
|
||||||
|
pre(node) {
|
||||||
|
delete node.properties.tabindex
|
||||||
|
delete node.properties.style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const vueRE = /-vue$/
|
||||||
|
const lineNoStartRE = /=(\d*)/
|
||||||
|
const lineNoRE = /:(no-)?line-numbers(=\d*)?$/
|
||||||
|
const mustacheRE = /\{\{.*?\}\}/g
|
||||||
|
|
||||||
|
return (str: string, lang: string, attrs: string) => {
|
||||||
|
const vPre = vueRE.test(lang) ? '' : 'v-pre'
|
||||||
|
lang =
|
||||||
|
lang
|
||||||
|
.replace(lineNoStartRE, '')
|
||||||
|
.replace(lineNoRE, '')
|
||||||
|
.replace(vueRE, '')
|
||||||
|
.toLowerCase() || defaultLang
|
||||||
|
|
||||||
|
if (lang) {
|
||||||
|
const langLoaded = highlighter.getLoadedLanguages().includes(lang as any)
|
||||||
|
if (!langLoaded && !isPlainLang(lang) && !isSpecialLang(lang)) {
|
||||||
|
logger.warn(
|
||||||
|
c.yellow(
|
||||||
|
`\nThe language '${lang}' is not loaded, falling back to '${defaultLang || 'txt'
|
||||||
|
}' for syntax highlighting.`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
lang = defaultLang
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const lineOptions = attrsToLines(attrs)
|
||||||
|
|
||||||
|
const mustaches = new Map<string, string>()
|
||||||
|
|
||||||
|
const removeMustache = (s: string) => {
|
||||||
|
if (vPre) return s
|
||||||
|
return s.replace(mustacheRE, (match) => {
|
||||||
|
let marker = mustaches.get(match)
|
||||||
|
if (!marker) {
|
||||||
|
marker = nanoid()
|
||||||
|
mustaches.set(match, marker)
|
||||||
|
}
|
||||||
|
return marker
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const restoreMustache = (s: string) => {
|
||||||
|
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'
|
||||||
|
) + '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
str = removeMustache(str).trimEnd()
|
||||||
|
|
||||||
|
const highlighted = highlighter.codeToHtml(str, {
|
||||||
|
lang,
|
||||||
|
transformers: [
|
||||||
|
...transformers,
|
||||||
|
// transformerCompactLineOptions(lineOptions),
|
||||||
|
// {
|
||||||
|
// name: 'vitepress:v-pre',
|
||||||
|
// pre(node) {
|
||||||
|
// if (vPre) node.properties['v-pre'] = ''
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
...userTransformers
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
__raw: attrs
|
||||||
|
},
|
||||||
|
...(typeof theme === 'string' || 'name' in theme
|
||||||
|
? { theme }
|
||||||
|
: {
|
||||||
|
themes: theme,
|
||||||
|
defaultColor: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return fillEmptyHighlightedLine(restoreMustache(highlighted))
|
||||||
|
}
|
||||||
|
}
|
||||||
6
plugins/plugin-shikiji/src/node/index.ts
Normal file
6
plugins/plugin-shikiji/src/node/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { shikijiPlugin } from './shikijiPlugin.js'
|
||||||
|
|
||||||
|
export * from './shikijiPlugin.js'
|
||||||
|
export * from './types.js'
|
||||||
|
|
||||||
|
export default shikijiPlugin
|
||||||
19
plugins/plugin-shikiji/src/node/shikijiPlugin.ts
Normal file
19
plugins/plugin-shikiji/src/node/shikijiPlugin.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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 const shikijiPlugin = (
|
||||||
|
options: ShikijiPluginOptions = {}): Plugin => ({
|
||||||
|
name: '@vuepress-plume/plugin-shikiji',
|
||||||
|
|
||||||
|
extendsMarkdown: async (md) => {
|
||||||
|
const theme = options.theme ?? { light: 'github-light', dark: 'github-dark' }
|
||||||
|
const highlighter = await highlight(theme, options)
|
||||||
|
|
||||||
|
md.options.highlight = highlighter
|
||||||
|
},
|
||||||
|
})
|
||||||
56
plugins/plugin-shikiji/src/node/types.ts
Normal file
56
plugins/plugin-shikiji/src/node/types.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import type {
|
||||||
|
BuiltinTheme,
|
||||||
|
Highlighter,
|
||||||
|
LanguageInput,
|
||||||
|
ShikijiTransformer,
|
||||||
|
ThemeRegistration,
|
||||||
|
} from 'shikiji'
|
||||||
|
export type ThemeOptions =
|
||||||
|
| ThemeRegistration
|
||||||
|
| BuiltinTheme
|
||||||
|
| {
|
||||||
|
light: ThemeRegistration | BuiltinTheme
|
||||||
|
dark: ThemeRegistration | BuiltinTheme
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HighlighterOptions {
|
||||||
|
/**
|
||||||
|
* Custom theme for syntax highlighting.
|
||||||
|
*
|
||||||
|
* You can also pass an object with `light` and `dark` themes to support dual themes.
|
||||||
|
*
|
||||||
|
* @example { theme: 'github-dark' }
|
||||||
|
* @example { theme: { light: 'github-light', dark: 'github-dark' } }
|
||||||
|
*
|
||||||
|
* You can use an existing theme.
|
||||||
|
* @see https://github.com/antfu/shikiji/blob/main/docs/themes.md#all-themes
|
||||||
|
* Or add your own theme.
|
||||||
|
* @see https://github.com/antfu/shikiji/blob/main/docs/themes.md#load-custom-themes
|
||||||
|
*/
|
||||||
|
theme?: ThemeOptions
|
||||||
|
/**
|
||||||
|
* Languages for syntax highlighting.
|
||||||
|
* @see https://github.com/antfu/shikiji/blob/main/docs/languages.md#all-themes
|
||||||
|
*/
|
||||||
|
languages?: LanguageInput[]
|
||||||
|
/**
|
||||||
|
* Custom language aliases.
|
||||||
|
*
|
||||||
|
* @example { 'my-lang': 'js' }
|
||||||
|
* @see https://github.com/antfu/shikiji/tree/main#custom-language-aliases
|
||||||
|
*/
|
||||||
|
languageAlias?: Record<string, string>
|
||||||
|
/**
|
||||||
|
* Setup Shikiji instance
|
||||||
|
*/
|
||||||
|
shikijiSetup?: (shikiji: Highlighter) => void | Promise<void>
|
||||||
|
/**
|
||||||
|
* Fallback language when the specified language is not available.
|
||||||
|
*/
|
||||||
|
defaultHighlightLang?: string
|
||||||
|
/**
|
||||||
|
* Transformers applied to code blocks
|
||||||
|
* @see https://github.com/antfu/shikiji#hast-transformers
|
||||||
|
*/
|
||||||
|
codeTransformers?: ShikijiTransformer[]
|
||||||
|
}
|
||||||
8
plugins/plugin-shikiji/tsconfig.build.json
Normal file
8
plugins/plugin-shikiji/tsconfig.build.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.build.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "./src",
|
||||||
|
"outDir": "./lib"
|
||||||
|
},
|
||||||
|
"include": ["./src"]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user