diff --git a/src/client/theme-default/styles/components/vp-doc.css b/src/client/theme-default/styles/components/vp-doc.css index 3e03783b..84e9d08c 100644 --- a/src/client/theme-default/styles/components/vp-doc.css +++ b/src/client/theme-default/styles/components/vp-doc.css @@ -310,30 +310,21 @@ } .vp-doc [class*='language-'] code { - display: block; - padding: 0 24px; - width: fit-content; + display: flex; + width: max-content; + min-width: 100%; + flex-direction: column; line-height: var(--vp-code-line-height); font-size: var(--vp-code-font-size); color: var(--vp-code-block-color); transition: color 0.5s; } -.vp-doc .highlight-lines { - position: absolute; - top: 0; - bottom: 0; - left: 0; - padding-top: 16px; - width: 100%; - line-height: var(--vp-code-line-height); - font-family: var(--vp-font-family-mono); - font-size: var(--vp-code-font-size); - user-select: none; - overflow: hidden; +.vp-doc [class*='language-'] code .line { + padding: 0 24px; } -.vp-doc .highlight-lines .highlighted { +.vp-doc [class*='language-'] code .highlighted { background-color: var(--vp-code-line-highlight-color); transition: background-color 0.5s; } diff --git a/src/node/markdown/plugins/highlight.ts b/src/node/markdown/plugins/highlight.ts index 0b373475..761aff42 100644 --- a/src/node/markdown/plugins/highlight.ts +++ b/src/node/markdown/plugins/highlight.ts @@ -1,7 +1,40 @@ -import { IThemeRegistration, getHighlighter } from 'shiki' +import { IThemeRegistration, getHighlighter, HtmlRendererOptions } from 'shiki' import type { ThemeOptions } from '../markdown' -export async function highlight(theme: ThemeOptions = 'material-palenight') { +/** + * 2 steps: + * + * 1. convert attrs into line numbers: + * {4,7-13,16,23-27,40} -> [4,7,8,9,10,11,12,13,16,23,24,25,26,27,40] + * 2. convert line numbers into line options: + * [{ line: number, classes: string[] }] + */ +const attrsToLines = (attrs: string): HtmlRendererOptions['lineOptions'] => { + const result: number[] = [] + if (!attrs.trim()) { + return [] + } + attrs + .split(',') + .map((v) => v.split('-').map((v) => parseInt(v, 10))) + .forEach(([start, end]) => { + if (start && end) { + result.push( + ...Array.from({ length: end - start + 1 }, (_, i) => start + i) + ) + } else { + result.push(start) + } + }) + return result.map((v) => ({ + line: v, + classes: ['highlighted'] + })) +} + +export async function highlight( + theme: ThemeOptions = 'material-palenight' +): Promise<(str: string, lang: string, attrs: string) => string> { const hasSingleTheme = typeof theme === 'string' || 'name' in theme const getThemeName = (themeValue: IThemeRegistration) => typeof themeValue === 'string' ? themeValue : themeValue.name @@ -12,22 +45,24 @@ export async function highlight(theme: ThemeOptions = 'material-palenight') { const preRE = /^/ const vueRE = /-vue$/ - return (str: string, lang: string) => { + return (str: string, lang: string, attrs: string) => { const vPre = vueRE.test(lang) ? '' : 'v-pre' lang = lang.replace(vueRE, '').toLowerCase() + const lineOptions = attrsToLines(attrs) + if (hasSingleTheme) { return highlighter - .codeToHtml(str, { lang, theme: getThemeName(theme) }) + .codeToHtml(str, { lang, lineOptions, theme: getThemeName(theme) }) .replace(preRE, `
`)
     }
 
     const dark = highlighter
-      .codeToHtml(str, { lang, theme: getThemeName(theme.dark) })
+      .codeToHtml(str, { lang, lineOptions, theme: getThemeName(theme.dark) })
       .replace(preRE, `
`)
 
     const light = highlighter
-      .codeToHtml(str, { lang, theme: getThemeName(theme.light) })
+      .codeToHtml(str, { lang, lineOptions, theme: getThemeName(theme.light) })
       .replace(preRE, `
`)
 
     return dark + light
diff --git a/src/node/markdown/plugins/highlightLines.ts b/src/node/markdown/plugins/highlightLines.ts
index f7e5293a..a1e6c65f 100644
--- a/src/node/markdown/plugins/highlightLines.ts
+++ b/src/node/markdown/plugins/highlightLines.ts
@@ -1,13 +1,15 @@
 // Modified from https://github.com/egoist/markdown-it-highlight-lines
+// Now this plugin is only used to normalize line attrs.
+// The else part of line highlights logic is in './highlight.ts'.
+
 import MarkdownIt from 'markdown-it'
 
 const RE = /{([\d,-]+)}/
-const wrapperRE = /^
/
 
 export const highlightLinePlugin = (md: MarkdownIt) => {
   const fence = md.renderer.rules.fence!
   md.renderer.rules.fence = (...args) => {
-    const [tokens, idx, options] = args
+    const [tokens, idx] = args
     const token = tokens[idx]
 
     // due to use of markdown-it-attrs, the {0} syntax would have been
@@ -40,35 +42,7 @@ export const highlightLinePlugin = (md: MarkdownIt) => {
       }
     }
 
-    const lineNumbers = lines
-      .split(',')
-      .map((v) => v.split('-').map((v) => parseInt(v, 10)))
-
-    const code = options.highlight
-      ? options.highlight(token.content, token.info, '')
-      : token.content
-
-    const rawCode = code.replace(wrapperRE, '')
-
-    const highlightLinesCode = rawCode
-      .split('\n')
-      .map((split, index) => {
-        const lineNumber = index + 1
-        const inRange = lineNumbers.some(([start, end]) => {
-          if (start && end) {
-            return lineNumber >= start && lineNumber <= end
-          }
-          return lineNumber === start
-        })
-        if (inRange) {
-          return `
 
` - } - return '
' - }) - .join('') - - const highlightLinesWrapperCode = `
${highlightLinesCode}
` - - return highlightLinesWrapperCode + code + token.info += ' ' + lines + return fence(...args) } }