From 3aae9d55cb536f9ce602af2ec7b1dd143486107b Mon Sep 17 00:00:00 2001 From: Antzy Date: Thu, 2 Nov 2023 23:17:23 +0800 Subject: [PATCH] feat(rehype): custom `parseMetaString` (#17) Co-authored-by: Anthony Fu --- packages/rehype-shikiji/src/index.ts | 29 +++++++++++++++++-- packages/rehype-shikiji/test/fixtures/a.md | 2 +- .../rehype-shikiji/test/fixtures/a.out.html | 2 +- packages/rehype-shikiji/test/index.test.ts | 9 ++++++ packages/shikiji/src/core/renderer-hast.ts | 1 + packages/shikiji/src/types.ts | 9 +++++- 6 files changed, 46 insertions(+), 6 deletions(-) diff --git a/packages/rehype-shikiji/src/index.ts b/packages/rehype-shikiji/src/index.ts index 3d73df038..f7e5283d8 100644 --- a/packages/rehype-shikiji/src/index.ts +++ b/packages/rehype-shikiji/src/index.ts @@ -3,7 +3,7 @@ import { bundledLanguages, getHighlighter } from 'shikiji' import { toString } from 'hast-util-to-string' import { visit } from 'unist-util-visit' import type { Plugin } from 'unified' -import type { Root } from 'hast' +import type { Element, Root } from 'hast' import { parseHighlightLines } from '../../shared/line-highlight' export type RehypeShikijiOptions = CodeOptionsThemes & { @@ -20,11 +20,28 @@ export type RehypeShikijiOptions = CodeOptionsThemes & { * @default true */ highlightLines?: boolean | string + + /** + * Extra meta data to pass to the highlighter + */ + meta?: Record + + /** + * Custom meta string parser + * Return an object to merge with `meta` + */ + parseMetaString?: ( + metaString: string, + node: Element, + tree: Root + ) => Record | undefined | null } const rehypeShikiji: Plugin<[RehypeShikijiOptions], Root> = function (options = {} as any) { const { highlightLines = true, + parseMetaString, + ...rest } = options const prefix = 'language-' @@ -64,12 +81,18 @@ const rehypeShikiji: Plugin<[RehypeShikijiOptions], Root> = function (options = return const code = toString(head as any) + const attrs = (head.data as any)?.meta + const meta = parseMetaString?.(attrs, node, tree) || {} + const codeOptions: CodeToHastOptions = { - ...options, + ...rest, lang: language.slice(prefix.length), + meta: { + ...rest.meta, + ...meta, + }, } - const attrs = (head.data as any)?.meta if (highlightLines && typeof attrs === 'string') { const lines = parseHighlightLines(attrs) if (lines) { diff --git a/packages/rehype-shikiji/test/fixtures/a.md b/packages/rehype-shikiji/test/fixtures/a.md index 7e6bf2cc6..27208110d 100644 --- a/packages/rehype-shikiji/test/fixtures/a.md +++ b/packages/rehype-shikiji/test/fixtures/a.md @@ -2,7 +2,7 @@ …world! -```js {3-4} +```js {3-4} fileName=test console.log('it works!') const a = 1 diff --git a/packages/rehype-shikiji/test/fixtures/a.out.html b/packages/rehype-shikiji/test/fixtures/a.out.html index f889c597f..d13fafd8f 100644 --- a/packages/rehype-shikiji/test/fixtures/a.out.html +++ b/packages/rehype-shikiji/test/fixtures/a.out.html @@ -1,6 +1,6 @@

Hello

…world!

-
console.log('it works!')
+
console.log('it works!')
 
 const a = 1
 console.log(a)
diff --git a/packages/rehype-shikiji/test/index.test.ts b/packages/rehype-shikiji/test/index.test.ts
index 4e0f65685..6fecfa89a 100644
--- a/packages/rehype-shikiji/test/index.test.ts
+++ b/packages/rehype-shikiji/test/index.test.ts
@@ -12,6 +12,15 @@ it('run', async () => {
     .use(remarkRehype)
     .use(rehypeShikiji, {
       theme: 'vitesse-light',
+      parseMetaString: (str) => {
+        return Object.fromEntries(str.split(' ').reduce((prev: [string, boolean | string][], curr: string) => {
+          const [key, value] = curr.split('=')
+          const isNormalKey = /^[A-Za-z0-9]+$/.test(key)
+          if (isNormalKey)
+            prev = [...prev, [key, value || true]]
+          return prev
+        }, []))
+      },
     })
     .use(rehypeStringify)
     .process(await fs.readFile(new URL('./fixtures/a.md', import.meta.url)))
diff --git a/packages/shikiji/src/core/renderer-hast.ts b/packages/shikiji/src/core/renderer-hast.ts
index f1d1609f5..5d13ec6cf 100644
--- a/packages/shikiji/src/core/renderer-hast.ts
+++ b/packages/shikiji/src/core/renderer-hast.ts
@@ -133,6 +133,7 @@ export function tokensToHast(
       style: options.rootStyle || `background-color:${options.bg};color:${options.fg}`,
       tabindex: '0',
       lang: options.lang,
+      ...options.meta,
     },
     children: [],
   }
diff --git a/packages/shikiji/src/types.ts b/packages/shikiji/src/types.ts
index 0c75cc38f..2b05a1eb7 100644
--- a/packages/shikiji/src/types.ts
+++ b/packages/shikiji/src/types.ts
@@ -215,9 +215,14 @@ export type CodeOptionsThemes =
   | CodeOptionsSingleTheme
   | CodeOptionsMultipleThemes
 
+export interface CodeOptionsMeta {
+  meta?: Record
+}
+
 export type CodeToHastOptions =
   & CodeToHastOptionsCommon
   & CodeOptionsThemes
+  & CodeOptionsMeta
 
 export interface ThemeRegistrationRaw extends IRawTheme {
 
@@ -280,7 +285,7 @@ export interface HastTransformers {
   token?: (hast: Element, line: number, col: number, lineElement: Element) => Element | void
 }
 
-export interface HtmlRendererOptions {
+export interface HtmlRendererOptionsCommon {
   lang?: string
   langId?: string
   fg?: string
@@ -308,6 +313,8 @@ export interface HtmlRendererOptions {
   mergeWhitespaces?: boolean
 }
 
+export type HtmlRendererOptions = HtmlRendererOptionsCommon & CodeToHastOptions
+
 export interface ThemedTokenScopeExplanation {
   scopeName: string
   themeMatches: any[]