diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 14324959..ce8dd787 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -2,6 +2,7 @@ import type { DefaultTheme } from 'vitepress' import { defineConfig } from 'vitepress' import { bundledThemes } from 'shikiji' import { defaultHoverInfoProcessor, transformerTwoslash } from 'vitepress-plugin-twoslash' +import { transformerRenderWhitespace } from 'shikiji-transformers' import { version } from '../../package.json' import vite from './vite.config' diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css index 5d581b46..27bc0da8 100644 --- a/docs/.vitepress/theme/style.css +++ b/docs/.vitepress/theme/style.css @@ -181,3 +181,20 @@ .DocSearch { --docsearch-primary-color: var(--vp-c-brand-1) !important; } + +.vp-code .tab, +.vp-code .space { + position: relative; +} + +.vp-code .tab::before { + content: '⇥'; + position: absolute; + opacity: 0.3; +} + +.vp-code .space::before { + content: '·'; + position: absolute; + opacity: 0.3; +} diff --git a/docs/packages/transformers.md b/docs/packages/transformers.md index 0ffa5d25..d036d254 100644 --- a/docs/packages/transformers.md +++ b/docs/packages/transformers.md @@ -60,6 +60,8 @@ export function foo() { } ``` +::: details HTML Output + ```html
 
@@ -78,6 +80,10 @@ export function foo() {
 
``` +::: + +--- + ### `transformerNotationHighlight` Use `[!code highlight]` to highlight a line (adding `highlighted` class). @@ -88,6 +94,10 @@ export function foo() { } ``` +Alternatively, you can use the [`transformerMetaHighlight`](#transformermetahighlight) to highlight lines based on the meta string. + +--- + ### `transformerNotationFocus` Use `[!code focus]` to focus a line (adding `focused` class). @@ -98,6 +108,8 @@ export function foo() { } ``` +--- + ### `transformerNotationErrorLevel` Use `[!code error]`, `[!code warning]`, to mark a line with an error level (adding `highlighted error`, `highlighted warning` class). @@ -109,18 +121,74 @@ export function foo() { } ``` +--- + ### `transformerRenderWhitespace` Render whitespaces (tabs and spaces) as individual spans, with classes `tab` and `space`. With some CSS, you can make it look like this: -image +
js
function block( ) {
+  space( )
+		table( ) 
+}
+ +::: details Example CSS + +```css +.vp-code .tab, +.vp-code .space { + position: relative; +} + +.vp-code .tab::before { + content: '⇥'; + position: absolute; + opacity: 0.3; +} + +.vp-code .space::before { + content: '·'; + position: absolute; + opacity: 0.3; +} +``` + +::: + +--- + +### `transformerMetaHighlight` + +Highlight lines based on the meta string provided on the code snippet. Requires integrations supports. + +````md +```js {1,3-4} +console.log('1') +console.log('2') +console.log('3') +console.log('4') +``` +```` + +Results in + +```js {1,3-4} +console.log('1') +console.log('2') +console.log('3') +console.log('4') +``` + +--- ### `transformerCompactLineOptions` Support for `shiki`'s `lineOptions` that is removed in `shikiji`. +--- + ### `transformerRemoveLineBreak` Remove line breaks between ``. Useful when you set `display: block` to `.line` in CSS. diff --git a/package.json b/package.json index 81f568b6..bf78f82e 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "rollup-plugin-esbuild": "^6.1.0", "rollup-plugin-typescript2": "^0.36.0", "shikiji": "workspace:*", + "shikiji-transformers": "workspace:*", "simple-git-hooks": "^2.9.0", "taze": "^0.13.1", "typescript": "^5.3.3", diff --git a/packages/markdown-it-shikiji/package.json b/packages/markdown-it-shikiji/package.json index a8a1460f..7ad3c858 100644 --- a/packages/markdown-it-shikiji/package.json +++ b/packages/markdown-it-shikiji/package.json @@ -51,7 +51,8 @@ }, "dependencies": { "markdown-it": "^14.0.0", - "shikiji": "workspace:*" + "shikiji": "workspace:*", + "shikiji-transformers": "workspace:*" }, "devDependencies": { "@types/markdown-it": "^13.0.7" diff --git a/packages/markdown-it-shikiji/src/core.ts b/packages/markdown-it-shikiji/src/core.ts index ffde57ff..f0e752c1 100644 --- a/packages/markdown-it-shikiji/src/core.ts +++ b/packages/markdown-it-shikiji/src/core.ts @@ -1,7 +1,6 @@ import type MarkdownIt from 'markdown-it' -import { addClassToHast } from 'shikiji/core' import type { BuiltinTheme, CodeOptionsMeta, CodeOptionsThemes, CodeToHastOptions, HighlighterGeneric, ShikijiTransformer, TransformerOptions } from 'shikiji' -import { parseHighlightLines } from '../../shared/line-highlight' +import { transformerMetaHighlight } from 'shikiji-transformers' export interface MarkdownItShikijiExtraOptions { /** @@ -54,21 +53,13 @@ export function setupMarkdownIt( const builtInTransformer: ShikijiTransformer[] = [] if (highlightLines) { - const lines = parseHighlightLines(attrs) - if (lines) { - const className = highlightLines === true - ? 'highlighted' - : highlightLines - - builtInTransformer.push({ - name: 'markdown-it-shikiji:line-class', - line(node, line) { - if (lines.includes(line)) - addClassToHast(node, className) - return node - }, - }) - } + builtInTransformer.push( + transformerMetaHighlight({ + className: highlightLines === true + ? 'highlighted' + : highlightLines, + }), + ) } builtInTransformer.push({ diff --git a/packages/rehype-shikiji/package.json b/packages/rehype-shikiji/package.json index 26ff62c1..78d7ac3b 100644 --- a/packages/rehype-shikiji/package.json +++ b/packages/rehype-shikiji/package.json @@ -53,6 +53,7 @@ "@types/hast": "^3.0.3", "hast-util-to-string": "^3.0.0", "shikiji": "workspace:*", + "shikiji-transformers": "workspace:*", "unified": "^11.0.4", "unist-util-visit": "^5.0.0" }, diff --git a/packages/rehype-shikiji/src/core.ts b/packages/rehype-shikiji/src/core.ts index 6da2b386..60161c0b 100644 --- a/packages/rehype-shikiji/src/core.ts +++ b/packages/rehype-shikiji/src/core.ts @@ -5,7 +5,7 @@ import type { BuiltinTheme } from 'shikiji' import type { Plugin } from 'unified' import { toString } from 'hast-util-to-string' import { visit } from 'unist-util-visit' -import { parseHighlightLines } from '../../shared/line-highlight' +import { transformerMetaHighlight } from 'shikiji-transformers' export interface MapLike { get(key: K): V | undefined @@ -133,22 +133,14 @@ const rehypeShikijiFromHighlighter: Plugin<[HighlighterGeneric, Rehype } if (highlightLines && typeof attrs === 'string') { - const lines = parseHighlightLines(attrs) - if (lines) { - const className = highlightLines === true - ? 'highlighted' - : highlightLines - - codeOptions.transformers ||= [] - codeOptions.transformers.push({ - name: 'rehype-shikiji:line-class', - line(node, line) { - if (lines.includes(line)) - addClassToHast(node, className) - return node - }, - }) - } + codeOptions.transformers ||= [] + codeOptions.transformers.push( + transformerMetaHighlight({ + className: highlightLines === true + ? 'highlighted' + : highlightLines, + }), + ) } try { diff --git a/packages/shared/line-highlight.ts b/packages/shared/line-highlight.ts deleted file mode 100644 index ce1ccf8b..00000000 --- a/packages/shared/line-highlight.ts +++ /dev/null @@ -1,17 +0,0 @@ -export function parseHighlightLines(attrs: string) { - if (!attrs) - return null - const match = attrs.match(/{([\d,-]+)}/) - if (!match) - return null - const lines = match[1].split(',') - .flatMap((v) => { - const num = v.split('-').map(v => Number.parseInt(v, 10)) - if (num.length === 1) - return [num[0]] - else - return Array.from({ length: num[1] - num[0] + 1 }, (_, i) => i + num[0]) - }) - - return lines -} diff --git a/packages/shared/test/line-highlight.test.ts b/packages/shared/test/line-highlight.test.ts deleted file mode 100644 index 00945808..00000000 --- a/packages/shared/test/line-highlight.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { expect, it } from 'vitest' -import { parseHighlightLines } from '../line-highlight' - -it('parseHighlightLines', () => { - expect(parseHighlightLines('')).toBe(null) - expect(parseHighlightLines('{1}')).toEqual([1]) - expect(parseHighlightLines('{1,2}')).toEqual([1, 2]) - expect(parseHighlightLines('{1,2-4,5}')).toEqual([1, 2, 3, 4, 5]) - expect(parseHighlightLines('{1-1}')).toEqual([1]) -}) diff --git a/packages/shikiji-transformers/src/index.ts b/packages/shikiji-transformers/src/index.ts index 49c7103b..21f2e05b 100644 --- a/packages/shikiji-transformers/src/index.ts +++ b/packages/shikiji-transformers/src/index.ts @@ -5,4 +5,5 @@ export * from './transformers/notation-focus' export * from './transformers/notation-highlight' export * from './transformers/notation-diff' export * from './transformers/notation-error-level' +export * from './transformers/transformer-meta-highlight' export * from './utils' diff --git a/packages/shikiji-transformers/src/transformers/transformer-meta-highlight.ts b/packages/shikiji-transformers/src/transformers/transformer-meta-highlight.ts new file mode 100644 index 00000000..9a3571d9 --- /dev/null +++ b/packages/shikiji-transformers/src/transformers/transformer-meta-highlight.ts @@ -0,0 +1,53 @@ +import { type ShikijiTransformer, addClassToHast } from 'shikiji' + +export function parseMetaHighlightString(meta: string) { + if (!meta) + return null + const match = meta.match(/{([\d,-]+)}/) + if (!match) + return null + const lines = match[1].split(',') + .flatMap((v) => { + const num = v.split('-').map(v => Number.parseInt(v, 10)) + if (num.length === 1) + return [num[0]] + else + return Array.from({ length: num[1] - num[0] + 1 }, (_, i) => i + num[0]) + }) + return lines +} + +export interface TransformerMetaHighlightOptions { + /** + * Class for highlighted lines + * + * @default 'highlighted' + */ + className?: string +} + +const symbol = Symbol('highlighted-lines') + +/** + * Allow using `{1,3-5}` in the code snippet meta to mark highlighted lines. + */ +export function transformerMetaHighlight( + options: TransformerMetaHighlightOptions = {}, +): ShikijiTransformer { + const { + className = 'highlighted', + } = options + + return { + name: 'shikiji-transformers:meta-highlight', + line(node, line) { + if (!this.options.meta?.__raw) + return + ;(this.meta as any)[symbol] ||= parseMetaHighlightString(this.options.meta.__raw) + const lines: number[] = (this.meta as any)[symbol] || [] + if (lines.includes(line)) + addClassToHast(node, className) + return node + }, + } +} diff --git a/packages/shikiji-transformers/test/meta-line-highlight.test.ts b/packages/shikiji-transformers/test/meta-line-highlight.test.ts new file mode 100644 index 00000000..874aaf80 --- /dev/null +++ b/packages/shikiji-transformers/test/meta-line-highlight.test.ts @@ -0,0 +1,10 @@ +import { expect, it } from 'vitest' +import { parseMetaHighlightString } from '../src/transformers/transformer-meta-highlight' + +it('parseHighlightLines', () => { + expect(parseMetaHighlightString('')).toBe(null) + expect(parseMetaHighlightString('{1}')).toEqual([1]) + expect(parseMetaHighlightString('{1,2}')).toEqual([1, 2]) + expect(parseMetaHighlightString('{1,2-4,5}')).toEqual([1, 2, 3, 4, 5]) + expect(parseMetaHighlightString('{1-1}')).toEqual([1]) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a686ac82..b49188cc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -137,6 +137,9 @@ importers: shikiji: specifier: workspace:* version: link:packages/shikiji + shikiji-transformers: + specifier: workspace:* + version: link:packages/shikiji-transformers simple-git-hooks: specifier: ^2.9.0 version: 2.9.0 @@ -216,6 +219,9 @@ importers: shikiji: specifier: workspace:* version: link:../shikiji + shikiji-transformers: + specifier: workspace:* + version: link:../shikiji-transformers devDependencies: '@types/markdown-it': specifier: ^13.0.7 @@ -232,6 +238,9 @@ importers: shikiji: specifier: workspace:* version: link:../shikiji + shikiji-transformers: + specifier: workspace:* + version: link:../shikiji-transformers unified: specifier: ^11.0.4 version: 11.0.4