Skip to content

Commit

Permalink
feat(remark-embed-snippet): embed specific lines (#21907)
Browse files Browse the repository at this point in the history
* feat(remark-embed-snippet): embed specific lines

* refactor(remark-embed-snippet): util file names

* fix(remark-embed-snippet): language of snippet

* feat(remark-embed-snippet): parse numeric range

Co-authored-by: gatsbybot <[email protected]>
  • Loading branch information
danpoq and gatsbybot authored Apr 10, 2020
1 parent 0b0f5f3 commit 109b905
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 6 deletions.
46 changes: 43 additions & 3 deletions packages/gatsby-remark-embed-snippet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,8 @@ The resulting HTML generated from the markdown file above would look something l

### Highlighting Lines

You can also specify specific lines for Prism to highlight using
`highlight-line` and `highlight-next-line` comments. You can also specify a
range of lines to highlight, relative to a `highlight-range` comment.
You can specify specific lines for Prism to highlight using
`highlight-line` and `highlight-next-line` comments. You can also specify a range of lines to highlight, relative to a `highlight-range` comment.

**JavaScript example**:

Expand Down Expand Up @@ -250,8 +249,49 @@ quz: "highlighted"
It's also possible to specify a range of lines to be hidden.
You can either specify line ranges in the embed using the syntax:
- #Lx - Embed one line from a file
- #Lx-y - Embed a range of lines from a file
- #Lx-y,a-b - Embed non-consecutive ranges of lines from a file
**Markdown example**:
```markdown
This is the JSX of my app:

`embed:App.js#L6-8`
```

With this example snippet:

```js
import React from "react"
import ReactDOM from "react-dom"

function App() {
return (
<div className="App">
<h1>Hello world</h1>
</div>
)
}
```

Will produce something like this:

```markdown
This is the JSX of my app:

<div className="App">
<h1>Hello world</h1>
</div>
```

**JavaScript example**:

You can also add `// hide-range` comments to your files.

```jsx
// hide-range{1-2}
import React from "react"
Expand Down
50 changes: 50 additions & 0 deletions packages/gatsby-remark-embed-snippet/src/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,56 @@ describe(`gatsby-remark-embed-snippet`, () => {
)
})

it(`should display a code block of a single line`, () => {
const codeBlockValue = ` console.log('hello world')`
fs.readFileSync.mockReturnValue(`function test() {
${codeBlockValue}
}`)

const markdownAST = remark.parse(`\`embed:hello-world.js#L2\``)
const transformed = plugin({ markdownAST }, { directory: `examples` })

const codeBlock = transformed.children[0].children[0]

expect(codeBlock.value).toEqual(codeBlockValue)
})

it(`should display a code block of a range of lines`, () => {
const codeBlockValue = ` if (window.location.search.indexOf('query') > -1) {
console.log('The user is searching')
}`
fs.readFileSync.mockReturnValue(`function test() {
${codeBlockValue}
}`)

const markdownAST = remark.parse(`\`embed:hello-world.js#L2-4\``)
const transformed = plugin({ markdownAST }, { directory: `examples` })

const codeBlock = transformed.children[0].children[0]

expect(codeBlock.value).toEqual(codeBlockValue)
})

it(`should display a code block of a range of non-consecutive lines`, () => {
const notInSnippet = `lineShouldNotBeInSnippet();`
fs.readFileSync.mockReturnValue(`function test() {
if (window.location.search.indexOf('query') > -1) {
console.log('The user is searching')
}
}
${notInSnippet}
window.addEventListener('resize', () => {
test();
})`)

const markdownAST = remark.parse(`\`embed:hello-world.js#L2-4,7-9\``)
const transformed = plugin({ markdownAST }, { directory: `examples` })

const codeBlock = transformed.children[0].children[0]

expect(codeBlock.value).not.toContain(notInSnippet)
})

it(`should error if an invalid file path is specified`, () => {
fs.existsSync.mockImplementation(path => path !== `examples/hello-world.js`)

Expand Down
27 changes: 24 additions & 3 deletions packages/gatsby-remark-embed-snippet/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const path = require(`path`)
const fs = require(`fs`)
const normalizePath = require(`normalize-path`)
const visit = require(`unist-util-visit`)
const rangeParser = require(`parse-numeric-range`)

// Language defaults to extension.toLowerCase();
// This map tracks languages that don't match their extension.
Expand Down Expand Up @@ -46,21 +47,41 @@ module.exports = ({ markdownAST, markdownNode }, { directory } = {}) => {

if (value.startsWith(`embed:`)) {
const file = value.substr(6)
const snippetPath = normalizePath(path.join(directory, file))
let snippetPath = normalizePath(path.join(directory, file))

// Embed specific lines numbers of a file
let lines = []
const rangePrefixIndex = snippetPath.indexOf(`#L`)
if (rangePrefixIndex > -1) {
const range = snippetPath.slice(rangePrefixIndex + 2)
if (range.length === 1) {
lines = [Number.parseInt(range, 10)]
} else {
lines = rangeParser.parse(range)
}
// Remove everything after the range prefix from file path
snippetPath = snippetPath.slice(0, rangePrefixIndex)
}

if (!fs.existsSync(snippetPath)) {
throw Error(`Invalid snippet specified; no such file "${snippetPath}"`)
}

const code = fs.readFileSync(snippetPath, `utf8`).trim()
let code = fs.readFileSync(snippetPath, `utf8`).trim()
if (lines.length) {
code = code
.split(`\n`)
.filter((_, lineNumber) => lines.includes(lineNumber + 1))
.join(`\n`)
}

// PrismJS's theme styles are targeting pre[class*="language-"]
// to apply its styles. We do the same here so that users
// can apply a PrismJS theme and get the expected, ready-to-use
// outcome without any additional CSS.
//
// @see https://github.com/PrismJS/prism/blob/1d5047df37aacc900f8270b1c6215028f6988eb1/themes/prism.css#L49-L54
const language = getLanguage(file)
const language = getLanguage(snippetPath)

// Change the node type to code, insert our file as value and set language.
node.type = `code`
Expand Down

0 comments on commit 109b905

Please sign in to comment.