Skip to content
This repository was archived by the owner on Feb 7, 2024. It is now read-only.

Commit ecf60cd

Browse files
fuma-namaantfu
andauthored
feat(transformers): two new transformers for word highlighting (#92)
Co-authored-by: Anthony Fu <[email protected]>
1 parent ac6298b commit ecf60cd

24 files changed

+361
-25
lines changed

docs/.vitepress/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { DefaultTheme } from 'vitepress'
22
import { defineConfig } from 'vitepress'
33
import { bundledThemes } from 'shikiji'
4+
import { transformerMetaWordHighlight, transformerNotationWordHighlight } from '../../packages/shikiji-transformers/src'
45
import { defaultHoverInfoProcessor, transformerTwoslash } from '../../packages/vitepress-plugin-twoslash/src/index'
56
import { version } from '../../package.json'
67
import vite from './vite.config'
@@ -54,6 +55,8 @@ export default defineConfig({
5455
}))
5556
},
5657
codeTransformers: [
58+
transformerMetaWordHighlight(),
59+
transformerNotationWordHighlight(),
5760
{
5861
// Render custom themes with codeblocks
5962
name: 'shikiji:inline-theme',

docs/.vitepress/theme/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'floating-vue/dist/style.css'
99
import '../../../packages/vitepress-plugin-twoslash/src/style.css'
1010
import 'uno.css'
1111
import './style.css'
12+
import './transformers.css'
1213

1314
export default {
1415
extends: Theme,

docs/.vitepress/theme/style.css

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -181,20 +181,3 @@
181181
.DocSearch {
182182
--docsearch-primary-color: var(--vp-c-brand-1) !important;
183183
}
184-
185-
.vp-code .tab,
186-
.vp-code .space {
187-
position: relative;
188-
}
189-
190-
.vp-code .tab::before {
191-
content: '⇥';
192-
position: absolute;
193-
opacity: 0.3;
194-
}
195-
196-
.vp-code .space::before {
197-
content: '·';
198-
position: absolute;
199-
opacity: 0.3;
200-
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
.vp-code .tab,
2+
.vp-code .space {
3+
position: relative;
4+
}
5+
6+
.vp-code .tab::before {
7+
content: '⇥';
8+
position: absolute;
9+
opacity: 0.3;
10+
}
11+
12+
.vp-code .space::before {
13+
content: '·';
14+
position: absolute;
15+
opacity: 0.3;
16+
}
17+
18+
.vp-code .highlighted-word {
19+
background-color: var(--vp-c-bg-soft);
20+
border: 1px solid var(--vp-c-border);
21+
padding: 1px 3px;
22+
margin: -1px -3px;
23+
border-radius: 4px;
24+
}

docs/packages/transformers.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,16 @@ export function foo() {
8888

8989
Use `[!code highlight]` to highlight a line (adding `highlighted` class).
9090

91+
````md
92+
```ts
93+
export function foo() {
94+
console.log('Highlighted') // [\!code highlight]
95+
}
96+
```
97+
````
98+
99+
Results in
100+
91101
```ts
92102
export function foo() {
93103
console.log('Highlighted') // [!code highlight]
@@ -98,10 +108,62 @@ Alternatively, you can use the [`transformerMetaHighlight`](#transformermetahigh
98108

99109
---
100110

111+
### `transformerNotationWordHighlight`
112+
113+
Use `[!code word:xxx]` to highlight a word (adding `highlighted-word` class).
114+
115+
````md
116+
```ts
117+
export function foo() { // [\!code word:Hello]
118+
const msg = 'Hello World'
119+
console.log(msg) // prints Hello World
120+
}
121+
```
122+
````
123+
124+
Results in
125+
126+
```ts
127+
export function foo() { // [!code word:Hello]
128+
const msg = 'Hello World'
129+
console.log(msg) // prints Hello World
130+
}
131+
```
132+
133+
You can also specify the number of occurrences to highlight, e.g. `[!code word:options:2]` will highlight the next 2 occurrences of `options`.
134+
135+
````md
136+
```ts
137+
// [\!code word:options:2]
138+
const options = { foo: 'bar' }
139+
options.foo = 'baz'
140+
console.log(options.foo) // this one will not be highlighted
141+
```
142+
````
143+
144+
```ts
145+
// [!code word:options:2]
146+
const options = { foo: 'bar' }
147+
options.foo = 'baz'
148+
console.log(options.foo) // this one will not be highlighted
149+
```
150+
151+
---
152+
101153
### `transformerNotationFocus`
102154

103155
Use `[!code focus]` to focus a line (adding `focused` class).
104156

157+
````md
158+
```ts
159+
export function foo() {
160+
console.log('Focused') // [\!code focus]
161+
}
162+
```
163+
````
164+
165+
Results in
166+
105167
```ts
106168
export function foo() {
107169
console.log('Focused') // [!code focus]
@@ -114,6 +176,17 @@ export function foo() {
114176

115177
Use `[!code error]`, `[!code warning]`, to mark a line with an error level (adding `highlighted error`, `highlighted warning` class).
116178

179+
````md
180+
```ts
181+
export function foo() {
182+
console.error('Error') // [\!code error]
183+
console.warn('Warning') // [\!code warning]
184+
}
185+
```
186+
````
187+
188+
Results in
189+
117190
```ts
118191
export function foo() {
119192
console.error('Error') // [!code error]
@@ -181,6 +254,25 @@ console.log('3')
181254
console.log('4')
182255
```
183256

257+
### `transformerMetaWordHighlight`
258+
259+
Highlight words based on the meta string provided on the code snippet. Requires integrations supports.
260+
261+
````md
262+
```js /Hello/
263+
const msg = 'Hello World'
264+
console.log(msg)
265+
console.log(msg) // prints Hello World
266+
```
267+
````
268+
269+
Results in
270+
271+
```js /Hello/
272+
const msg = 'Hello World'
273+
console.log(msg) // prints Hello World
274+
```
275+
184276
---
185277

186278
### `transformerCompactLineOptions`

packages/rehype-shikiji/test/fixtures/a.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
…world!
44

5-
```js {3-4} fileName=test
5+
```js {3-4} fileName=test /a/
66
console.log('it works!')
77

88
const a = 1

packages/rehype-shikiji/test/fixtures/a.out.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ <h1>Hello</h1>
22
<p>…world!</p>
33
<pre class="shiki vitesse-light" style="background-color:#ffffff;color:#393a34" tabindex="0" fileName="test"><code><span class="line"><span style="color:#B07D48">console</span><span style="color:#999999">.</span><span style="color:#59873A">log</span><span style="color:#999999">(</span><span style="color:#B5695999">'</span><span style="color:#B56959">it works!</span><span style="color:#B5695999">'</span><span style="color:#999999">)</span></span>
44
<span class="line"></span>
5-
<span class="line highlighted"><span style="color:#AB5959">const</span><span style="color:#B07D48"> a</span><span style="color:#999999"> =</span><span style="color:#2F798A"> 1</span></span>
6-
<span class="line highlighted"><span style="color:#B07D48">console</span><span style="color:#999999">.</span><span style="color:#59873A">log</span><span style="color:#999999">(</span><span style="color:#B07D48">a</span><span style="color:#999999">)</span></span>
5+
<span class="line highlighted"><span style="color:#AB5959">const</span><span style="color:#B07D48"> </span><span style="color:#B07D48" class="highlighted-word">a</span><span style="color:#999999"> =</span><span style="color:#2F798A"> 1</span></span>
6+
<span class="line highlighted"><span style="color:#B07D48">console</span><span style="color:#999999">.</span><span style="color:#59873A">log</span><span style="color:#999999">(</span><span style="color:#B07D48" class="highlighted-word">a</span><span style="color:#999999">)</span></span>
77
<span class="line"></span></code></pre>

packages/rehype-shikiji/test/index.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import remarkParse from 'remark-parse'
44
import remarkRehype from 'remark-rehype'
55
import rehypeStringify from 'rehype-stringify'
66
import { expect, it } from 'vitest'
7+
import { transformerMetaHighlight, transformerMetaWordHighlight } from 'shikiji-transformers'
78
import rehypeShikiji from '../src'
89

910
it('run', async () => {
@@ -12,7 +13,10 @@ it('run', async () => {
1213
.use(remarkRehype)
1314
.use(rehypeShikiji, {
1415
theme: 'vitesse-light',
15-
highlightLines: true,
16+
transformers: [
17+
transformerMetaWordHighlight(),
18+
transformerMetaHighlight(),
19+
],
1620
parseMetaString: (str) => {
1721
return Object.fromEntries(str.split(' ').reduce((prev: [string, boolean | string][], curr: string) => {
1822
const [key, value] = curr.split('=')

packages/shikiji-transformers/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ export * from './transformers/remove-line-breaks'
33
export * from './transformers/compact-line-options'
44
export * from './transformers/notation-focus'
55
export * from './transformers/notation-highlight'
6+
export * from './transformers/notation-highlight-word'
7+
export * from './transformers/transformer-meta-highlight-word'
68
export * from './transformers/notation-diff'
79
export * from './transformers/notation-error-level'
810
export * from './transformers/transformer-meta-highlight'
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { Element } from 'hast'
2+
import { addClassToHast } from 'shikiji/core'
3+
4+
export function highlightWordInLine(line: Element, ignoredElement: Element | null, word: string, className: string): void {
5+
line.children = line.children.flatMap((span) => {
6+
if (span.type !== 'element' || span.tagName !== 'span' || span === ignoredElement)
7+
return span
8+
9+
const textNode = span.children[0]
10+
11+
if (textNode.type !== 'text')
12+
return span
13+
14+
return replaceSpan(span, textNode.value, word, className) ?? span
15+
})
16+
}
17+
18+
function inheritElement(original: Element, overrides: Partial<Element>): Element {
19+
return {
20+
...original,
21+
properties: {
22+
...original.properties,
23+
},
24+
...overrides,
25+
}
26+
}
27+
28+
function replaceSpan(span: Element, text: string, word: string, className: string): Element[] | undefined {
29+
const index = text.indexOf(word)
30+
31+
if (index === -1)
32+
return
33+
34+
const createNode = (value: string) => inheritElement(span, {
35+
children: [
36+
{
37+
type: 'text',
38+
value,
39+
},
40+
],
41+
})
42+
43+
const nodes: Element[] = []
44+
45+
if (index > 0)
46+
nodes.push(createNode(text.slice(0, index)))
47+
48+
const highlightedNode = createNode(word)
49+
addClassToHast(highlightedNode, className)
50+
nodes.push(highlightedNode)
51+
52+
if (index + word.length < text.length)
53+
nodes.push(createNode(text.slice(index + word.length)))
54+
55+
return nodes
56+
}

0 commit comments

Comments
 (0)