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:
-
+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