Skip to content

Commit 2693ab9

Browse files
authored
Merge pull request #11 from timlrx/feat/code-highlight-style
Feat/code highlight style
2 parents 87e0903 + 54c9b3e commit 2693ab9

File tree

5 files changed

+86
-61
lines changed

5 files changed

+86
-61
lines changed

README.md

+30-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# rehype-prism-plus
22

3+
![sample-code-block-output](sample-code-block.png)
4+
35
[rehype](https://github.com/wooorm/rehype) plugin to highlight code blocks in HTML with [Prism] (via [refractor]) with additional line highlighting and line numbers functionalities.
46

57
Inspired by and uses a compatible API as [@mapbox/rehype-prism](https://github.com/mapbox/rehype-prism) with additional support for line-highlighting and line numbers.
@@ -91,34 +93,56 @@ HTML Output:
9193

9294
## Styling
9395

94-
Here's a sample stylesheet:
96+
To style the language tokens, you can just copy them from any prismjs compatible ones. Here's a list of [themes](https://github.com/PrismJS/prism-themes).
97+
98+
In addition, the following styles should be added for line highlighting and line numbers to work correctly:
9599

96100
```css
101+
pre {
102+
overflow-x: auto;
103+
}
104+
105+
/**
106+
* Inspired by gatsby remark prism - https://www.gatsbyjs.com/plugins/gatsby-remark-prismjs/
107+
* 1. Make the element just wide enough to fit its content.
108+
* 2. Always fill the visible space in .code-highlight.
109+
*/
110+
.code-highlight {
111+
float: left; /* 1 */
112+
min-width: 100%; /* 2 */
113+
}
114+
97115
.code-line {
116+
display: block;
98117
padding-left: 16px;
118+
padding-right: 16px;
99119
margin-left: -16px;
100120
margin-right: -16px;
101121
border-left-width: 4px;
102-
border-left-color: rgb(31, 41, 55); \\ Set to code block color
122+
border-left-color: rgb(31, 41, 55); /* Set code block color */
103123
}
104124

105125
.highlight-line {
106126
margin-left: -16px;
107127
margin-right: -16px;
108-
background-color: rgba(55, 65, 81, 0.5); \\ Highlight color
128+
background-color: rgba(55, 65, 81, 0.5); /* Set highlight bg color */
109129
border-left-width: 4px;
110-
border-left-color: rgb(59, 130, 246);
130+
border-left-color: rgb(59, 130, 246); /* Set highlight accent border color */
111131
}
112132

113133
.line-number::before {
114134
padding-right: 16px;
115135
margin-left: -8px;
116-
color: rgb(156, 163, 175); \\ Line number color
136+
color: rgb(156, 163, 175); /* Line number color */
117137
content: attr(line);
118138
}
119139
```
120140

121-
For styling of language tokens, consult [refractor] and [Prism].
141+
Here's the styled output using the prism-night-owl theme:
142+
143+
![sample-code-block-output](sample-code-block.png)
144+
145+
For more information on styling of language tokens, consult [refractor] and [Prism].
122146

123147
## API
124148

index.js

+18-19
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ const splitLine = (text) => {
7676
return textArray.map((line) => {
7777
return {
7878
type: 'element',
79-
tagName: 'div',
79+
tagName: 'span',
8080
properties: { className: ['code-line'] },
81-
children: [{ type: 'text', value: line === '' ? '\n' : line }],
81+
children: [{ type: 'text', value: line }],
8282
}
8383
})
8484
}
@@ -164,12 +164,8 @@ const rehypePrism = (options = {}) => {
164164
/** @type {string} */
165165
// @ts-ignore
166166
let meta = node.data && node.data.meta ? node.data.meta : ''
167-
168-
if (lang) {
169-
parent.properties.className = (parent.properties.className || []).concat('language-' + lang)
170-
// Add lang to meta to allow line highlighting even when no lang is specified
171-
meta = `${lang} ${meta}`
172-
}
167+
node.properties.className = node.properties.className || []
168+
node.properties.className.push('code-highlight')
173169

174170
let refractorRoot
175171
let langError = false
@@ -179,17 +175,22 @@ const rehypePrism = (options = {}) => {
179175
try {
180176
// @ts-ignore
181177
refractorRoot = refractor.highlight(toString(node), lang)
182-
refractorRoot = getNodePosition(refractorRoot)
183-
refractorRoot.children = splitTextByLine(refractorRoot.children)
184178
} catch (err) {
185179
if (options.ignoreMissing && /Unknown language/.test(err.message)) {
186180
langError = true
181+
refractorRoot = node.children
187182
} else {
188183
throw err
189184
}
190185
}
186+
} else {
187+
refractorRoot = node.children
191188
}
192189

190+
// @ts-ignore
191+
refractorRoot = getNodePosition(refractorRoot)
192+
refractorRoot.children = splitTextByLine(refractorRoot.children)
193+
193194
const shouldHighlightLine = calculateLinesToHighlight(meta)
194195
// @ts-ignore
195196
const codeLineArray = splitLine(toString(node))
@@ -198,22 +199,20 @@ const rehypePrism = (options = {}) => {
198199
// Code lines
199200
if (meta.toLowerCase().includes('showLineNumbers'.toLowerCase()) || options.showLineNumbers) {
200201
line.properties.line = [(i + 1).toString()]
201-
line.properties.className = [`${line.properties.className} line-number`]
202+
line.properties.className.push('line-number')
202203
}
203204

204205
// Line highlight
205206
if (shouldHighlightLine(i)) {
206-
line.properties.className = [`${line.properties.className} highlight-line`]
207+
line.properties.className.push('highlight-line')
207208
}
208209

209210
// Syntax highlight
210-
if (lang && line.children && !langError) {
211-
const treeExtract = filter(
212-
refractorRoot,
213-
(node) => node.position.start.line <= i + 1 && node.position.end.line >= i + 1
214-
)
215-
line.children = treeExtract.children
216-
}
211+
const treeExtract = filter(
212+
refractorRoot,
213+
(node) => node.position.start.line <= i + 1 && node.position.end.line >= i + 1
214+
)
215+
line.children = treeExtract.children
217216
}
218217

219218
node.children = codeLineArray

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "rehype-prism-plus",
3-
"version": "0.0.6",
3+
"version": "0.1.0",
44
"description": "rehype plugin to highlight code blocks in HTML with Prism (via refractor) with line highlighting and line numbers",
55
"source": "index.js",
66
"files": [

sample-code-block.png

9.45 KB
Loading

test.js

+37-35
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,21 @@ const processHtml = (html, options, metastring) => {
2828
.toString()
2929
}
3030

31-
test('copies the language- class to pre tag', () => {
31+
test('adds a code-highlight class to the code tag', () => {
3232
const result = processHtml(dedent`
3333
<pre><code class="language-py"></code></pre>
3434
`)
35-
const expected = dedent`<pre class="language-py"><code class="language-py"></code></pre>`
35+
const expected = dedent`<pre><code class="language-py code-highlight"></code></pre>`
3636
assert.is(result, expected)
3737
})
3838

39-
test('add div with class code line for each line', () => {
40-
const result = processHtml(dedent`
39+
test('add span with class code line for each line', () => {
40+
const result = processHtml(
41+
dedent`
4142
<pre><code>x = 6</code></pre>
42-
`)
43-
const expected = dedent`<pre><code><div class="code-line">x = 6</div></code></pre>`
43+
`
44+
)
45+
const expected = dedent`<pre><code class="code-highlight"><span class="code-line">x = 6</span></code></pre>`
4446
assert.is(result, expected)
4547
})
4648

@@ -54,7 +56,7 @@ test('finds code and highlights', () => {
5456
const expected = dedent`
5557
<div>
5658
<p>foo</p>
57-
<pre class="language-py"><code class="language-py"><div class="code-line">x <span class="token operator">=</span> <span class="token number">6</span></div></code></pre>
59+
<pre><code class="language-py code-highlight"><span class="code-line">x <span class="token operator">=</span> <span class="token number">6</span></span></code></pre>
5860
</div>
5961
`
6062
assert.is(result, expected)
@@ -71,10 +73,10 @@ y
7173
`).trim()
7274
const expected = dedent`
7375
<div>
74-
<pre class="language-py"><code class="language-py"><div class="code-line">x
75-
</div><div class="code-line">
76-
</div><div class="code-line">y
77-
</div></code></pre>
76+
<pre><code class="language-py code-highlight"><span class="code-line">x
77+
</span><span class="code-line">
78+
</span><span class="code-line">y
79+
</span></code></pre>
7880
</div>
7981
`
8082
assert.is(result, expected)
@@ -90,7 +92,7 @@ test('handles uppercase correctly', () => {
9092
const expected = dedent`
9193
<div>
9294
<p>foo</p>
93-
<pre class="language-py"><code class="language-PY"><div class="code-line">x <span class="token operator">=</span> <span class="token number">6</span></div></code></pre>
95+
<pre><code class="language-PY code-highlight"><span class="code-line">x <span class="token operator">=</span> <span class="token number">6</span></span></code></pre>
9496
</div>
9597
`
9698
assert.is(result, expected)
@@ -101,13 +103,13 @@ test('each line of code should be a separate div', async () => {
101103
<div>
102104
<p>foo</p>
103105
<pre>
104-
<code class="language-py">x = 6
106+
<code class="language-py code-highlight">x = 6
105107
y = 7
106108
</code>
107109
</pre>
108110
</div>
109111
`).trim()
110-
const codeLineCount = (result.match(/<div class="code-line">/g) || []).length
112+
const codeLineCount = (result.match(/<span class="code-line">/g) || []).length
111113
assert.is(codeLineCount, 2)
112114
})
113115

@@ -117,16 +119,16 @@ test('should highlight line', async () => {
117119
dedent`
118120
<div>
119121
<pre>
120-
<code class="language-py">x = 6
121-
y = 7
122+
<code class="language-py code-highlight">x = 6
123+
y = 7
122124
</code>
123125
</pre>
124126
</div>
125127
`,
126128
{},
127129
meta
128130
).trim()
129-
const codeHighlightCount = (result.match(/<div class="code-line highlight-line">/g) || []).length
131+
const codeHighlightCount = (result.match(/<span class="code-line highlight-line">/g) || []).length
130132
assert.is(codeHighlightCount, 1)
131133
})
132134

@@ -136,8 +138,8 @@ test('should highlight comma separated lines', async () => {
136138
dedent`
137139
<div>
138140
<pre>
139-
<code class="language-py">x = 6
140-
y = 7
141+
<code class="language-py code-highlight">x = 6
142+
y = 7
141143
z = 10
142144
</code>
143145
</pre>
@@ -146,7 +148,7 @@ test('should highlight comma separated lines', async () => {
146148
{},
147149
meta
148150
).trim()
149-
const codeHighlightCount = (result.match(/<div class="code-line highlight-line">/g) || []).length
151+
const codeHighlightCount = (result.match(/<span class="code-line highlight-line">/g) || []).length
150152
assert.is(codeHighlightCount, 2)
151153
})
152154

@@ -156,8 +158,8 @@ test('should should parse ranges with a space in between', async () => {
156158
dedent`
157159
<div>
158160
<pre>
159-
<code class="language-py">x = 6
160-
y = 7
161+
<code class="language-py code-highlight">x = 6
162+
y = 7
161163
z = 10
162164
</code>
163165
</pre>
@@ -166,7 +168,7 @@ test('should should parse ranges with a space in between', async () => {
166168
{},
167169
meta
168170
).trim()
169-
const codeHighlightCount = (result.match(/<div class="code-line highlight-line">/g) || []).length
171+
const codeHighlightCount = (result.match(/<span class="code-line highlight-line">/g) || []).length
170172
assert.is(codeHighlightCount, 2)
171173
})
172174

@@ -176,8 +178,8 @@ test('should highlight range separated lines', async () => {
176178
dedent`
177179
<div>
178180
<pre>
179-
<code class="language-py">x = 6
180-
y = 7
181+
<code class="language-py code-highlight">x = 6
182+
y = 7
181183
z = 10
182184
</code>
183185
</pre>
@@ -186,7 +188,7 @@ test('should highlight range separated lines', async () => {
186188
{},
187189
meta
188190
).trim()
189-
const codeHighlightCount = (result.match(/<div class="code-line highlight-line">/g) || []).length
191+
const codeHighlightCount = (result.match(/<span class="code-line highlight-line">/g) || []).length
190192
assert.is(codeHighlightCount, 3)
191193
})
192194

@@ -195,7 +197,7 @@ test('showLineNumbers option add line numbers', async () => {
195197
dedent`
196198
<div>
197199
<pre>
198-
<code class="language-py">x = 6
200+
<code class="language-py code-highlight">x = 6
199201
y = 7
200202
</code>
201203
</pre>
@@ -214,7 +216,7 @@ test('showLineNumbers property works in meta field', async () => {
214216
dedent`
215217
<div>
216218
<pre>
217-
<code class="language-py">x = 6
219+
<code class="language-py code-highlight">x = 6
218220
y = 7
219221
</code>
220222
</pre>
@@ -235,7 +237,7 @@ test('should support both highlighting and add line number', async () => {
235237
<div>
236238
<pre>
237239
<code class="language-py">x = 6
238-
y = 7
240+
y = 7
239241
z = 10
240242
</code>
241243
</pre>
@@ -267,7 +269,7 @@ test('with options.ignoreMissing, does nothing to code block with fake language-
267269
`,
268270
{ ignoreMissing: true }
269271
)
270-
const expected = dedent`<pre class="language-thisisnotalanguage"><code class="language-thisisnotalanguage"><div class="code-line">x = 6</div></code></pre>`
272+
const expected = dedent`<pre><code class="language-thisisnotalanguage code-highlight"><span class="code-line">x = 6</span></code></pre>`
271273
assert.is(result, expected)
272274
})
273275

@@ -282,11 +284,11 @@ test('should work with multiline code / comments', () => {
282284
`,
283285
{ ignoreMissing: true }
284286
)
285-
const expected = dedent`<pre class="language-js"><code class="language-js"><div class="code-line">
286-
</div><div class="code-line"><span class="token doc-comment comment">/**
287-
</span></div><div class="code-line"><span class="token doc-comment comment"> * My comment
288-
</span></div><div class="code-line"><span class="token doc-comment comment"> */</span>
289-
</div></code></pre>`
287+
const expected = dedent`<pre><code class="language-js code-highlight"><span class="code-line">
288+
</span><span class="code-line"><span class="token doc-comment comment">/**
289+
</span></span><span class="code-line"><span class="token doc-comment comment"> * My comment
290+
</span></span><span class="code-line"><span class="token doc-comment comment"> */</span>
291+
</span></code></pre>`
290292
assert.is(result, expected)
291293
})
292294

0 commit comments

Comments
 (0)