|
|
|
@ -23,7 +23,7 @@ const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10)
|
|
|
|
|
* 2. convert line numbers into line options:
|
|
|
|
|
* [{ line: number, classes: string[] }]
|
|
|
|
|
*/
|
|
|
|
|
const attrsToLines = (attrs: string): TransformerCompactLineOption[] => {
|
|
|
|
|
function attrsToLines(attrs: string): TransformerCompactLineOption[] {
|
|
|
|
|
attrs = attrs.replace(/^(?:\[.*?\])?.*?([\d,-]+).*/, '$1').trim()
|
|
|
|
|
const result: number[] = []
|
|
|
|
|
if (!attrs) {
|
|
|
|
@ -51,7 +51,7 @@ export async function highlight(
|
|
|
|
|
theme: ThemeOptions,
|
|
|
|
|
options: MarkdownOptions,
|
|
|
|
|
logger: Pick<Logger, 'warn'> = console
|
|
|
|
|
): Promise<(str: string, lang: string, attrs: string) => string> {
|
|
|
|
|
): Promise<[(str: string, lang: string, attrs: string) => string, () => void]> {
|
|
|
|
|
const {
|
|
|
|
|
defaultHighlightLang: defaultLang = '',
|
|
|
|
|
codeTransformers: userTransformers = []
|
|
|
|
@ -95,93 +95,96 @@ export async function highlight(
|
|
|
|
|
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
|
|
|
|
|
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 && !isSpecialLang(lang)) {
|
|
|
|
|
logger.warn(
|
|
|
|
|
c.yellow(
|
|
|
|
|
`\nThe language '${lang}' is not loaded, falling back to '${
|
|
|
|
|
defaultLang || 'txt'
|
|
|
|
|
}' for syntax highlighting.`
|
|
|
|
|
if (lang) {
|
|
|
|
|
const langLoaded = highlighter.getLoadedLanguages().includes(lang)
|
|
|
|
|
if (!langLoaded && !isSpecialLang(lang)) {
|
|
|
|
|
logger.warn(
|
|
|
|
|
c.yellow(
|
|
|
|
|
`\nThe language '${lang}' is not loaded, falling back to '${
|
|
|
|
|
defaultLang || 'txt'
|
|
|
|
|
}' for syntax highlighting.`
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
lang = defaultLang
|
|
|
|
|
lang = defaultLang
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const lineOptions = attrsToLines(attrs)
|
|
|
|
|
const mustaches = new Map<string, string>()
|
|
|
|
|
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 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 restoreMustache = (s: string) => {
|
|
|
|
|
mustaches.forEach((marker, match) => {
|
|
|
|
|
s = s.replaceAll(marker, match)
|
|
|
|
|
})
|
|
|
|
|
return s
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
str = removeMustache(str).trimEnd()
|
|
|
|
|
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'] = ''
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'vitepress:empty-line',
|
|
|
|
|
code(hast) {
|
|
|
|
|
hast.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: []
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
...userTransformers
|
|
|
|
|
],
|
|
|
|
|
meta: { __raw: attrs },
|
|
|
|
|
...(typeof theme === 'object' && 'light' in theme && 'dark' in theme
|
|
|
|
|
? { themes: theme, defaultColor: false }
|
|
|
|
|
: { theme })
|
|
|
|
|
})
|
|
|
|
|
const highlighted = highlighter.codeToHtml(str, {
|
|
|
|
|
lang,
|
|
|
|
|
transformers: [
|
|
|
|
|
...transformers,
|
|
|
|
|
transformerCompactLineOptions(lineOptions),
|
|
|
|
|
{
|
|
|
|
|
name: 'vitepress:v-pre',
|
|
|
|
|
pre(node) {
|
|
|
|
|
if (vPre) node.properties['v-pre'] = ''
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'vitepress:empty-line',
|
|
|
|
|
code(hast) {
|
|
|
|
|
hast.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: []
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
...userTransformers
|
|
|
|
|
],
|
|
|
|
|
meta: { __raw: attrs },
|
|
|
|
|
...(typeof theme === 'object' && 'light' in theme && 'dark' in theme
|
|
|
|
|
? { themes: theme, defaultColor: false }
|
|
|
|
|
: { theme })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return restoreMustache(highlighted)
|
|
|
|
|
}
|
|
|
|
|
return restoreMustache(highlighted)
|
|
|
|
|
},
|
|
|
|
|
highlighter.dispose
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|