Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add line numbers to prismjs plugin #5821

Merged
Merged
1 change: 1 addition & 0 deletions examples/using-remark/src/layouts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "typeface-space-mono"
import "typeface-spectral"

import "prismjs/themes/prism-solarizedlight.css"
import "prismjs/plugins/line-numbers/prism-line-numbers.css"

class Layout extends React.Component {
render() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ No language indicated, so no syntax highlighting.
But let's throw in a <b>tag</b>.
```

## Line highlighting
## Line highlighting & numbering

[gatsby-remark-prismjs][1] has its own line highlighting implementation which
[gatsby-remark-prismjs][1] has its own line highlighting & numbering implementation which
differs a bit from PrismJS's own. You can find out everything about it in the
[corresponding README][1].

```javascript{1-2,22}
```javascript{1-2,22}{numberLines: true}
// In your gatsby-config.js
// Let's make this line very long so that our container has to scroll its overflow…
plugins: [
Expand Down Expand Up @@ -99,7 +99,7 @@ differs a bit from PrismJS's own. You can find out everything about it in the
]
```

```javascript{1-2,22}
```javascript{1-2,22}{numberLines: true}
// In your gatsby-config.js
// Let's make this line very long so that our container has to scroll its overflow…
plugins: [
Expand Down Expand Up @@ -128,6 +128,20 @@ plugins: [
]
```

Line numbers can start from anywhere, here's an example showing a small extract from a larger chunk of code:

```{numberLines: 549}
...
a long imaginary code block
...
```

```{numberLines: 549}
...
a long imaginary code block
...
```

Let's do something crazy and add a list with another code example:

- **A list item**
Expand Down
3 changes: 3 additions & 0 deletions examples/using-remark/src/utils/typography.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ const options = {
minWidth: `100%`,
textShadow: `none`,
},
".gatsby-highlight pre[class*='language-'].line-numbers": {
paddingLeft: `2.8em`,
},
".gatsby-highlight-code-line": {
background: `#fff2cc`,
display: `block`,
Expand Down
1 change: 1 addition & 0 deletions packages/gatsby-remark-prismjs/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/index.js
/add-line-numbers.js
/highlight-code.js
/parse-line-number-range.js
/load-prism-language.js
Expand Down
66 changes: 66 additions & 0 deletions packages/gatsby-remark-prismjs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ plugins: [
// the language "sh" which will highlight using the
// bash highlighter.
aliases: {},
// This toggles the display of line numbers alongside the code.
// To use it, add the following line in src/layouts/index.js
// right after importing the prism color scheme:
// `require("prismjs/plugins/line-numbers/prism-line-numbers.css");`
// Defaults to false.
showLineNumbers: false
},
},
],
Expand Down Expand Up @@ -110,6 +116,7 @@ CSS along your PrismJS theme and the styles for `.gatsby-highlight-code-line`:
* padding and overflow.
* 1. Make the element just wide enough to fit its content.
* 2. Always fill the visible space in .gatsby-highlight.
* 3. Adjust the position of the line numbers
*/
.gatsby-highlight pre[class*="language-"] {
background-color: transparent;
Expand All @@ -119,6 +126,20 @@ CSS along your PrismJS theme and the styles for `.gatsby-highlight-code-line`:
float: left; /* 1 */
min-width: 100%; /* 2 */
}
.gatsby-highlight pre[class*="language-"].line-numbers {
paddingLeft: 2.8em; /* 3 */
}
```

#### Optional: Add line numbering

If you want to add line numbering alongside your code, you need to
import the corresponding CSS file from PrismJS, right after importing your
colorscheme in `layout/index.js`:

```javascript
// layouts/index.js
require("prismjs/plugins/line-numbers/prism-line-numbers.css");
```

### Usage in Markdown
Expand All @@ -139,6 +160,39 @@ This is some beautiful code:
]
```

To see the line numbers alongside your code, you can use the `numberLines` option:

```javascript{numberLines: true}
// In your gatsby-config.js
plugins: [
{
resolve: `gatsby-transformer-remark`,
options: {
plugins: [
`gatsby-remark-prismjs`,
]
}
}
]
```

You can also start numbering at any index you wish (here, numbering
will start at index 5):

```javascript{numberLines: 5}
// In your gatsby-config.js
plugins: [
{
resolve: `gatsby-transformer-remark`,
options: {
plugins: [
`gatsby-remark-prismjs`,
]
}
}
]
```

You can also add line highlighting. It adds a span around lines of code with a
special class `.gatsby-highlight-code-line` that you can target with styles. See
this README for more info.
Expand Down Expand Up @@ -204,9 +258,21 @@ throw our overflow and background on `.gatsby-highlight`, and use
`display:block` on `.gatsby-highlight-code-line` – all of this coming together
to facilitate the desired line highlight behavior.

### Line numbering

Because [the line numbering PrismJS plugin][7] runs client-side, a few adaptations were required to make it work:

* A class `.line-numbers` is dynamically added to the `<pre>` element.
* A new node `<span class="line-numbers-rows">` is added right before the closing `</pre>`
containing as many empty `<span>`s as there are lines.

See the [client-side PrismJS implementation][8] for reference.

[1]: https://github.com/PrismJS/prism/tree/8eb0ab6f76484ca47fa7acbf77657fab17b03ca7/plugins/line-highlight
[2]: https://github.com/facebook/react/blob/00ba97a354e841701b4b83983c3a3904895e7b87/docs/_config.yml#L10
[3]: http://prismjs.com/#plugins
[4]: https://facebook.github.io/react/tutorial/tutorial.html
[5]: https://github.com/PrismJS/prism/tree/1d5047df37aacc900f8270b1c6215028f6988eb1/themes
[6]: http://prismjs.com/
[7]: https://prismjs.com/plugins/line-numbers/
[8]: https://github.com/PrismJS/prism/blob/master/plugins/line-numbers/prism-line-numbers.js#L69-L115
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ Object {
},
},
"type": "html",
"value": "<div class=\\"gatsby-highlight\\" data-language=\\"js\\">
<pre class=\\"custom-js\\"><code class=\\"custom-js\\"><span class=\\"token comment\\">// Fake</span></code></pre>
</div>",
"value": "<div class=\\"gatsby-highlight\\" data-language=\\"js\\"><pre class=\\"custom-js\\"><code class=\\"custom-js\\"><span class=\\"token comment\\">// Fake</span></code></pre></div>",
},
],
"position": Object {
Expand Down Expand Up @@ -65,9 +63,7 @@ Object {
},
},
"type": "html",
"value": "<div class=\\"gatsby-highlight\\" data-language=\\"javascript\\">
<pre class=\\"language-javascript\\"><code class=\\"language-javascript\\">// Fake</code></pre>
</div>",
"value": "<div class=\\"gatsby-highlight\\" data-language=\\"javascript\\"><pre class=\\"language-javascript\\"><code class=\\"language-javascript\\">// Fake</code></pre></div>",
},
],
"position": Object {
Expand Down Expand Up @@ -108,9 +104,7 @@ Object {
},
},
"type": "html",
"value": "<div class=\\"gatsby-highlight\\" data-language=\\"js\\">
<pre class=\\"language-js\\"><code class=\\"language-js\\"><span class=\\"token comment\\">// Fake</span></code></pre>
</div>",
"value": "<div class=\\"gatsby-highlight\\" data-language=\\"js\\"><pre class=\\"language-js\\"><code class=\\"language-js\\"><span class=\\"token comment\\">// Fake</span></code></pre></div>",
},
],
"position": Object {
Expand Down Expand Up @@ -348,3 +342,47 @@ Object {
"type": "root",
}
`;

exports[`remark prism plugin numberLines adds line-number markup when necessary 1`] = `
Object {
"children": Array [
Object {
"lang": "js{numberLines:5}",
"position": Position {
"end": Object {
"column": 4,
"line": 4,
"offset": 46,
},
"indent": Array [
1,
1,
1,
],
"start": Object {
"column": 1,
"line": 1,
"offset": 0,
},
},
"type": "html",
"value": "<div class=\\"gatsby-highlight\\" data-language=\\"js\\"><pre style=\\"counter-reset: linenumber 4\\" class=\\"language-js line-numbers\\"><code class=\\"language-js\\"><span class=\\"token comment\\">//.foo { </span>
color<span class=\\"token punctuation\\">:</span> red<span class=\\"token punctuation\\">;</span>
<span class=\\"token punctuation\\">}</span>\`</code><span aria-hidden=\\"true\\" class=\\"line-numbers-rows\\" style=\\"white-space: normal; width: auto; left: 0;\\"><span></span><span></span><span></span></span></pre></div>",
},
],
"position": Object {
"end": Object {
"column": 4,
"line": 4,
"offset": 46,
},
"start": Object {
"column": 1,
"line": 1,
"offset": 0,
},
},
"type": "root",
}
`;
17 changes: 17 additions & 0 deletions packages/gatsby-remark-prismjs/src/__tests__/add-line-numbers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const addLineNumbers = require(`../add-line-numbers`)

describe(`returns the line numbers container`, () => {
it(`should return the <span> container with the right classes`, () => {
expect(addLineNumbers(``)).toEqual(
`<span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;">`
+ `</span>`
)
})
it(`should return return as many <span></span> children as there are code lines`, () => {
expect(addLineNumbers(`line1\nline2\nline3`)).toEqual(
`<span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;">`
+ `<span></span><span></span><span></span>`
+ `</span>`
)
})
})
9 changes: 9 additions & 0 deletions packages/gatsby-remark-prismjs/src/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,13 @@ describe(`remark prism plugin`, () => {
expect(markdownAST).toMatchSnapshot()
})
})

describe(`numberLines`, () => {
it(`adds line-number markup when necessary`, () => {
const code = `\`\`\`js{numberLines:5}\n//.foo { \ncolor: red;\n }\``
const markdownAST = remark.parse(code)
plugin({ markdownAST })
expect(markdownAST).toMatchSnapshot()
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,67 @@ describe(`parses numeric ranges from the languages markdown code directive`, ()
])
})

describe(`parses line numbering options from the languages markdown code directive`, () => {
it(`parses the right line number start index from the languages variable`, () => {
expect(parseLineNumberRange(`jsx{numberLines: true}`).numberLines).toEqual(true)
expect(parseLineNumberRange(`jsx{numberLines: true}`).numberLinesStartAt).toEqual(1)
expect(parseLineNumberRange(`jsx{numberLines: 3}`).numberLines).toEqual(true)
expect(parseLineNumberRange(`jsx{numberLines: 3}`).numberLinesStartAt).toEqual(3)
})

it(`parses the right line number start index without a specified language`, () => {
expect(parseLineNumberRange(`{numberLines: true}`).numberLines).toEqual(true)
expect(parseLineNumberRange(`{numberLines: true}`).numberLinesStartAt).toEqual(1)
expect(parseLineNumberRange(`{numberLines: 3}`).numberLines).toEqual(true)
expect(parseLineNumberRange(`{numberLines: 3}`).numberLinesStartAt).toEqual(3)
})

it(`ignores non-true or non-number values`, () => {
expect(parseLineNumberRange(`jsx{numberLines: false}`).numberLines).toEqual(false)
expect(parseLineNumberRange(`jsx{numberLines: NaN}`).numberLines).toEqual(false)
})

it(`casts decimals line number start into the nearest lower integer`, () => {
expect(parseLineNumberRange(`jsx{numberLines: 1.2}`).numberLinesStartAt).toEqual(1)
expect(parseLineNumberRange(`jsx{numberLines: 1.8}`).numberLinesStartAt).toEqual(1)
})
})

describe(`parses both line numbering and line highlighting options`, () => {
it(`one line highlighted`, () => {
expect(parseLineNumberRange(`jsx{1}{numberLines: 3}`)).toEqual({
splitLanguage: `jsx`,
highlightLines: [1],
numberLines: true,
numberLinesStartAt: 3,
})
})
it(`multiple lines highlighted`, () => {
expect(parseLineNumberRange(`jsx{1,5,7-8}{numberLines: 3}`)).toEqual({
splitLanguage: `jsx`,
highlightLines: [1,5,7,8],
numberLines: true,
numberLinesStartAt: 3,
})
})
it(`numberLines: true`, () => {
expect(parseLineNumberRange(`jsx{1,5,7-8}{numberLines: true}`)).toEqual({
splitLanguage: `jsx`,
highlightLines: [1,5,7,8],
numberLines: true,
numberLinesStartAt: 1,
})
})
it(`reverse ordering`, () => {
expect(parseLineNumberRange(`jsx{numberLines: 4}{2}`)).toEqual({
splitLanguage: `jsx`,
highlightLines: [2],
numberLines: true,
numberLinesStartAt: 4,
})
})
})

it(`handles bad inputs`, () => {
expect(parseLineNumberRange(`jsx{-1`).highlightLines).toEqual([])
expect(parseLineNumberRange(`jsx{-1....`).highlightLines).toEqual([])
Expand Down
22 changes: 22 additions & 0 deletions packages/gatsby-remark-prismjs/src/add-line-numbers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module.exports = (code = []) => {

// Generate as many `<span></span>` as there are code lines
const generateSpans = (numberOfLines) => {
let spans = ``
for (let i=0; i<numberOfLines; i++) {
spans += `<span></span>`
}
return spans
}

const numberOfLines = code.length === 0 ? 0 : code.split(`\n`).length

// Generate the container for the line numbers.
// Relevant code in the Prism Line Numbers plugin can be found here:
// https://github.com/PrismJS/prism/blob/f356dfe71bf126e6dc060c03f3e042de28a9bec4/plugins/line-numbers/prism-line-numbers.js#L99-L115
const lineNumbersWrapper =
`<span aria-hidden="true" class="line-numbers-rows" style="white-space: normal; width: auto; left: 0;">`
+ `${generateSpans(numberOfLines)}`
+ `</span>`
return lineNumbersWrapper
}
Loading