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 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) } }