Skip to content

Commit

Permalink
feat: allow highlight directives in prismjs plugin
Browse files Browse the repository at this point in the history
Heavily inspired by DSchau#2
  • Loading branch information
phacks committed Nov 17, 2018
1 parent fb7abb5 commit 7489a21
Showing 16 changed files with 519 additions and 99 deletions.

Large diffs are not rendered by default.

152 changes: 152 additions & 0 deletions packages/gatsby-remark-prismjs/highlight-line-range.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"use strict"

const rangeParser = require(`parse-numeric-range`)

const COMMENT_START = /(#|\/\/|\{\/\*|\/\*+|<!--)/
const COMMENT_END = /(-->|\*\/\}|\*\/)?/
const DIRECTIVE = /highlight-(next-line|line|start|end|range)({([^}]+)})?/
const END_DIRECTIVE = /highlight-end/
const plainTextWithLFTest = /<span class="token plain-text">[^<]*\n[^<]*<\/span>/g

const stripComment = line =>
line.replace(
new RegExp(
`\\s*${COMMENT_START.source}\\s*${DIRECTIVE.source}\\s*${
COMMENT_END.source
}`
),
``
)

const wrap = line =>
[`<span class="gatsby-highlight-code-line">`, `${line}\n`, `</span>`].join(``)

const wrapAndStripComment = line => wrap(stripComment(line))

const getHighlights = (line, code, index) => {
const [, directive, directiveRange] = line.match(DIRECTIVE)

switch (directive) {
case `next-line`:
return [
{
code: wrap(code[index + 1]),
highlighted: true,
},
index + 1,
]

case `start`: {
const endIndex = code.findIndex(line => END_DIRECTIVE.test(line))
const end = endIndex === -1 ? code.length : endIndex
const highlighted = code.slice(index + 1, end).map(line => {
return {
code: wrap(line),
highlighted: true,
}
})
return [highlighted, end]
}

case `line`:
return [
{
code: wrapAndStripComment(line),
highlighted: true,
},
index,
]

case `range`:
// if range is not provided we ignore the directive
if (!directiveRange) {
return [
{
code: code[index + 1],
highlighted: false,
},
index + 1,
]
} else {
const strippedDirectiveRange = directiveRange.slice(1, -1)
const range = rangeParser.parse(strippedDirectiveRange)

if (range.length > 0) {
// if current line is 10 and range is {1-5, 7}, lastLineIndexInRange === 17
let lastLineIndexInRange = index + 1 + range[range.length - 1] // if range goes farther than code length, make lastLineIndexInRange equal to code length

if (lastLineIndexInRange > code.length) {
lastLineIndexInRange = code.length
}

const highlighted = code
.slice(index + 1, lastLineIndexInRange)
.map((line, idx) => {
return {
code: range.includes(idx + 1) ? wrap(line) : line,
highlighted: range.includes(idx + 1),
}
})
return [highlighted, lastLineIndexInRange]
} // if range is incorrect we ignore the directive

return [
{
code: code[index + 1],
highlighted: false,
},
index + 1,
]
}

default:
return [
{
code: wrap(line),
highlighted: true,
},
index,
]
}
}

module.exports = function highlightLineRange(code, highlights = []) {
let highlighted = []
const split = code.split(`\n`)

if (highlights.length > 0) {
// HACK split plain-text spans with line separators inside into multiple plain-text spans
// separatered by line separator - this fixes line highlighting behaviour for jsx
code = code.replace(plainTextWithLFTest, match =>
match.replace(/\n/g, `</span>\n<span class="token plain-text">`)
)
return split.map((line, i) => {
if (highlights.includes(i + 1)) {
return {
highlighted: true,
code: line,
}
}

return {
code: line,
}
})
}

for (let i = 0; i < split.length; i++) {
const line = split[i]

if (DIRECTIVE.test(line)) {
const [highlights, index] = getHighlights(line, split, i)
highlighted = highlighted.concat(highlights)
i = index
} else {
highlighted.push({
code: line,
})
}
}

return highlighted
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`highlight code and lines with PrismJS for language cpp 1`] = `
"<span class=\\"gatsby-highlight-code-line\\">
</span><span class=\\"gatsby-highlight-code-line\\"><span class=\\"token keyword\\">int</span> <span class=\\"token function\\">sum</span><span class=\\"token punctuation\\">(</span>a<span class=\\"token punctuation\\">,</span> b<span class=\\"token punctuation\\">)</span> <span class=\\"token punctuation\\">{</span>
</span> <span class=\\"token keyword\\">return</span> a <span class=\\"token operator\\">+</span> b<span class=\\"token punctuation\\">;</span>
"<span class=\\"token keyword\\">int</span> <span class=\\"token function\\">sum</span><span class=\\"token punctuation\\">(</span>a<span class=\\"token punctuation\\">,</span> b<span class=\\"token punctuation\\">)</span> <span class=\\"token punctuation\\">{</span> <span class=\\"token keyword\\">return</span> a <span class=\\"token operator\\">+</span> b<span class=\\"token punctuation\\">;</span>
<span class=\\"token punctuation\\">}</span>
"
`;
@@ -20,16 +18,13 @@ exports[`highlight code and lines with PrismJS for language jsx 1`] = `
<span class=\\"token function\\">render</span><span class=\\"token punctuation\\">(</span><span class=\\"token punctuation\\">)</span> <span class=\\"token punctuation\\">{</span>
<span class=\\"token keyword\\">return</span> <span class=\\"token punctuation\\">(</span>
<span class=\\"gatsby-highlight-code-line\\"> <span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;</span>div</span><span class=\\"token punctuation\\">></span></span><span class=\\"token plain-text\\"></span>
</span><span class=\\"gatsby-highlight-code-line\\"><span class=\\"token plain-text\\"> </span><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;</span>h1</span><span class=\\"token punctuation\\">></span></span><span class=\\"token plain-text\\">Counter</span><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;/</span>h1</span><span class=\\"token punctuation\\">></span></span><span class=\\"token plain-text\\"></span>
</span><span class=\\"token plain-text\\"> </span><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;</span>p</span><span class=\\"token punctuation\\">></span></span><span class=\\"token plain-text\\">current count: </span><span class=\\"token punctuation\\">{</span><span class=\\"token keyword\\">this</span><span class=\\"token punctuation\\">.</span>state<span class=\\"token punctuation\\">.</span>count<span class=\\"token punctuation\\">}</span><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;/</span>p</span><span class=\\"token punctuation\\">></span></span><span class=\\"token plain-text\\"></span>
<span class=\\"gatsby-highlight-code-line\\"><span class=\\"token plain-text\\"> </span><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;</span>button</span> <span class=\\"token attr-name\\">onClick</span><span class=\\"token script language-javascript\\"><span class=\\"token script-punctuation punctuation\\">=</span><span class=\\"token punctuation\\">{</span><span class=\\"token punctuation\\">(</span><span class=\\"token punctuation\\">)</span> <span class=\\"token operator\\">=></span> <span class=\\"token keyword\\">this</span><span class=\\"token punctuation\\">.</span><span class=\\"token function\\">setState</span><span class=\\"token punctuation\\">(</span><span class=\\"token punctuation\\">{</span> count<span class=\\"token punctuation\\">:</span> <span class=\\"token keyword\\">this</span><span class=\\"token punctuation\\">.</span>state<span class=\\"token punctuation\\">.</span>count <span class=\\"token operator\\">+</span> <span class=\\"token number\\">1</span> <span class=\\"token punctuation\\">}</span><span class=\\"token punctuation\\">)</span><span class=\\"token punctuation\\">}</span></span><span class=\\"token punctuation\\">></span></span><span class=\\"token plain-text\\"></span>
</span><span class=\\"token plain-text\\"> plus</span>
<span class=\\"token plain-text\\"> </span><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;/</span>button</span><span class=\\"token punctuation\\">></span></span><span class=\\"token plain-text\\"></span>
<span class=\\"token plain-text\\"> </span><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;</span>button</span> <span class=\\"token attr-name\\">onClick</span><span class=\\"token script language-javascript\\"><span class=\\"token script-punctuation punctuation\\">=</span><span class=\\"token punctuation\\">{</span><span class=\\"token punctuation\\">(</span><span class=\\"token punctuation\\">)</span> <span class=\\"token operator\\">=></span> <span class=\\"token keyword\\">this</span><span class=\\"token punctuation\\">.</span><span class=\\"token function\\">setState</span><span class=\\"token punctuation\\">(</span><span class=\\"token punctuation\\">{</span> count<span class=\\"token punctuation\\">:</span> <span class=\\"token keyword\\">this</span><span class=\\"token punctuation\\">.</span>state<span class=\\"token punctuation\\">.</span>count <span class=\\"token operator\\">-</span> <span class=\\"token number\\">1</span> <span class=\\"token punctuation\\">}</span><span class=\\"token punctuation\\">)</span><span class=\\"token punctuation\\">}</span></span><span class=\\"token punctuation\\">></span></span><span class=\\"token plain-text\\"></span>
<span class=\\"token plain-text\\"> minus</span>
<span class=\\"token plain-text\\"> </span><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;/</span>button</span><span class=\\"token punctuation\\">></span></span><span class=\\"token plain-text\\"></span>
<span class=\\"token plain-text\\"> </span><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;/</span>div</span><span class=\\"token punctuation\\">></span></span>
<span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;</span>div</span><span class=\\"token punctuation\\">></span></span><span class=\\"token plain-text\\"> </span><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;</span>h1</span><span class=\\"token punctuation\\">></span></span><span class=\\"token plain-text\\">Counter</span><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;/</span>h1</span><span class=\\"token punctuation\\">></span></span><span class=\\"token plain-text\\"> </span><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;</span>p</span><span class=\\"token punctuation\\">></span></span><span class=\\"token plain-text\\">current count: </span><span class=\\"token punctuation\\">{</span><span class=\\"token keyword\\">this</span><span class=\\"token punctuation\\">.</span>state<span class=\\"token punctuation\\">.</span>count<span class=\\"token punctuation\\">}</span><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;/</span>p</span><span class=\\"token punctuation\\">></span></span><span class=\\"token plain-text\\">
</span><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;</span>button</span> <span class=\\"token attr-name\\">onClick</span><span class=\\"token script language-javascript\\"><span class=\\"token script-punctuation punctuation\\">=</span><span class=\\"token punctuation\\">{</span><span class=\\"token punctuation\\">(</span><span class=\\"token punctuation\\">)</span> <span class=\\"token operator\\">=></span> <span class=\\"token keyword\\">this</span><span class=\\"token punctuation\\">.</span><span class=\\"token function\\">setState</span><span class=\\"token punctuation\\">(</span><span class=\\"token punctuation\\">{</span> count<span class=\\"token punctuation\\">:</span> <span class=\\"token keyword\\">this</span><span class=\\"token punctuation\\">.</span>state<span class=\\"token punctuation\\">.</span>count <span class=\\"token operator\\">+</span> <span class=\\"token number\\">1</span> <span class=\\"token punctuation\\">}</span><span class=\\"token punctuation\\">)</span><span class=\\"token punctuation\\">}</span></span><span class=\\"token punctuation\\">></span></span><span class=\\"token plain-text\\"> plus
</span><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;/</span>button</span><span class=\\"token punctuation\\">></span></span><span class=\\"token plain-text\\">
</span><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;</span>button</span> <span class=\\"token attr-name\\">onClick</span><span class=\\"token script language-javascript\\"><span class=\\"token script-punctuation punctuation\\">=</span><span class=\\"token punctuation\\">{</span><span class=\\"token punctuation\\">(</span><span class=\\"token punctuation\\">)</span> <span class=\\"token operator\\">=></span> <span class=\\"token keyword\\">this</span><span class=\\"token punctuation\\">.</span><span class=\\"token function\\">setState</span><span class=\\"token punctuation\\">(</span><span class=\\"token punctuation\\">{</span> count<span class=\\"token punctuation\\">:</span> <span class=\\"token keyword\\">this</span><span class=\\"token punctuation\\">.</span>state<span class=\\"token punctuation\\">.</span>count <span class=\\"token operator\\">-</span> <span class=\\"token number\\">1</span> <span class=\\"token punctuation\\">}</span><span class=\\"token punctuation\\">)</span><span class=\\"token punctuation\\">}</span></span><span class=\\"token punctuation\\">></span></span><span class=\\"token plain-text\\">
minus
</span><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;/</span>button</span><span class=\\"token punctuation\\">></span></span><span class=\\"token plain-text\\">
</span><span class=\\"token tag\\"><span class=\\"token tag\\"><span class=\\"token punctuation\\">&lt;/</span>div</span><span class=\\"token punctuation\\">></span></span>
<span class=\\"token punctuation\\">)</span>
<span class=\\"token punctuation\\">}</span>
<span class=\\"token punctuation\\">}</span>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`highlighting a line range highlight-line highlights line 1`] = `
"<span class=\\"gatsby-highlight-code-line\\"> return \\"hello world\\"
</span>"
`;
exports[`highlighting a line range highlight-next-line highlights correct line 1`] = `
"<span class=\\"gatsby-highlight-code-line\\"> return \\"hello world\\"
</span>"
`;
exports[`highlighting a line range highlight-start / highlight-end highlights correct lines 1`] = `
"<span class=\\"gatsby-highlight-code-line\\"> var a = \\"b\\"
</span>
<span class=\\"gatsby-highlight-code-line\\"> return a + \\"hello world\\"
</span>"
`;
exports[`highlighting a line range highlight-start / highlight-end highlights without end directive 1`] = `
"<span class=\\"gatsby-highlight-code-line\\">var a = \\"b\\"
</span>
<span class=\\"gatsby-highlight-code-line\\">return a + \\"hello world\\"
</span>
<span class=\\"gatsby-highlight-code-line\\">
</span>"
`;
exports[`highlighting a line range jsx comment highlights comment line 1`] = `
"<span class=\\"gatsby-highlight-code-line\\"> <button>sup</button>
</span>"
`;
exports[`highlighting a line range yaml highlights yaml 1`] = `
"<span class=\\"gatsby-highlight-code-line\\">- title: catorce
</span>"
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!doctype html>
<html>
<head>
<title>Helloooooooooo</title>
</head>
<body>
<h1>hello world</h1> <!-- highlight-line -->
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from "react"
export default function Button() {
return (
<div>
<button>sup</button> {/* highlight-line */}
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React, { Component } from "react" // highlight-line
export default class Counter extends Component {
/* highlight-start */
state = {
count: 0,
}
// highlight-end
updateCount = () => {
this.setState(state => ({
// highlight-next-line
count: state.count + 1,
}))
}
render() {
const { count } = this.state // highlight-line
return (
<div>
<span>clicked {count}</span>
{/* highlight-start */}
<button onClick={this.updateCount}>Click me</button>
{/* highlight-end */}
</div>
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function test() {
return "hello world" // highlight-line
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function test() {
// highlight-next-line
return "hello world"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
function test() {
// highlight-start
var a = "b"
return a + "hello world"
// highlight-end
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// highlight-start
var a = "b"
return a + "hello world"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- title: uno
- title: dos
- title: catorce # highlight-line
12 changes: 12 additions & 0 deletions packages/gatsby-remark-prismjs/src/__tests__/fixtures/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const fs = require(`fs-extra`)
const path = require(`path`)
const base = __dirname
module.exports = fs.readdirSync(base).reduce((lookup, file) => {
if (file !== `index.js`) {
const name = file
.replace(/-(\w)/g, (_, char) => char.toUpperCase())
.replace(/\..+/, ``)
lookup[name] = fs.readFileSync(path.join(base, file), `utf8`)
}
return lookup
}, {})
Loading

0 comments on commit 7489a21

Please sign in to comment.