Skip to content

feat(markdown): snippet partial import #2225

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

Merged
merged 3 commits into from
May 8, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ exports[`snippet import snippet 1`] = `
</code></pre>
`;

exports[`snippet import snippet with region and highlight 1`] = `
<pre><code class="language-js{1,3}">function foo () {
// ..
}</code></pre>
`;

exports[`snippet import snippet with highlight multiple lines 1`] = `
<div class="highlight-lines">
<div class="highlighted">&nbsp;</div>
Expand All @@ -35,3 +41,16 @@ exports[`snippet import snippet with highlight single line 1`] = `
// ..
}
`;

exports[`snippet import snippet with indented region 1`] = `
<pre><code class="language-html">&lt;section&gt;
&lt;h1&gt;Hello World&lt;/h1&gt;
&lt;/section&gt;
&lt;div&gt;Lorem Ipsum&lt;/div&gt;</code></pre>
`;

exports[`snippet import snippet with region 1`] = `
<pre><code class="language-js">function foo () {
// ..
}</code></pre>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<<< @/packages/@vuepress/markdown/__tests__/fragments/snippet-with-indented-region.html#body
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<<< @/packages/@vuepress/markdown/__tests__/fragments/snippet-with-region.js#snippet{1,3}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<<< @/packages/@vuepress/markdown/__tests__/fragments/snippet-with-region.js#snippet
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- #region body -->
<section>
<h1>Hello World</h1>
</section>
<div>Lorem Ipsum</div>
<!-- #endregion body -->
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// #region snippet
function foo () {
// ..
}
// #endregion snippet

export default foo
18 changes: 18 additions & 0 deletions packages/@vuepress/markdown/__tests__/snippet.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,22 @@ describe('snippet', () => {
const output = mdH.render(input)
expect(output).toMatchSnapshot()
})

test('import snippet with region', () => {
const input = getFragment(__dirname, 'code-snippet-with-region.md')
const output = md.render(input)
expect(output).toMatchSnapshot()
})

test('import snippet with region and highlight', () => {
const input = getFragment(__dirname, 'code-snippet-with-region-and-highlight.md')
const output = md.render(input)
expect(output).toMatchSnapshot()
})

test('import snippet with indented region', () => {
const input = getFragment(__dirname, 'code-snippet-with-indented-region.md')
const output = md.render(input)
expect(output).toMatchSnapshot()
})
})
107 changes: 100 additions & 7 deletions packages/@vuepress/markdown/lib/snippet.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,104 @@
const { fs, logger, path } = require('@vuepress/shared-utils')

function dedent (text) {
const wRegexp = /^([ \t]*)(.*)\n/gm
let match; let minIndentLength = null

while ((match = wRegexp.exec(text)) !== null) {
const [indentation, content] = match.slice(1)
if (!content) continue

const indentLength = indentation.length
if (indentLength > 0) {
minIndentLength
= minIndentLength !== null
? Math.min(minIndentLength, indentLength)
: indentLength
} else break
}

if (minIndentLength) {
text = text.replace(
new RegExp(`^[ \t]{${minIndentLength}}(.*)`, 'gm'),
'$1'
)
}

return text
}

function testLine (line, regexp, regionName, end = false) {
const [full, tag, name] = regexp.exec(line.trim()) || []

return (
full
&& tag
&& name === regionName
&& tag.match(end ? /^[Ee]nd ?[rR]egion$/ : /^[rR]egion$/)
)
}

function findRegion (lines, regionName) {
const regionRegexps = [
/^\/\/ ?#?((?:end)?region) ([\w*-]+)$/, // javascript, typescript, java
/^\/\* ?#((?:end)?region) ([\w*-]+) ?\*\/$/, // css, less, scss
/^#pragma ((?:end)?region) ([\w*-]+)$/, // C, C++
/^<!-- #?((?:end)?region) ([\w*-]+) -->$/, // HTML, markdown
/^#((?:End )Region) ([\w*-]+)$/, // Visual Basic
/^::#((?:end)region) ([\w*-]+)$/, // Bat
/^# ?((?:end)?region) ([\w*-]+)$/ // C#, PHP, Powershell, Python, perl & misc
]

let regexp = null
let start = -1

for (const [lineId, line] of lines.entries()) {
if (regexp === null) {
for (const reg of regionRegexps) {
if (testLine(line, reg, regionName)) {
start = lineId + 1
regexp = reg
break
}
}
} else if (testLine(line, regexp, regionName, true)) {
return { start, end: lineId, regexp }
}
}

return null
}

module.exports = function snippet (md, options = {}) {
const fence = md.renderer.rules.fence
const root = options.root || process.cwd()

md.renderer.rules.fence = (...args) => {
const [tokens, idx, , { loader }] = args
const token = tokens[idx]
const { src } = token
const [src, regionName] = token.src ? token.src.split('#') : ['']
if (src) {
if (loader) {
loader.addDependency(src)
}
if (fs.existsSync(src)) {
token.content = fs.readFileSync(src, 'utf8')
let content = fs.readFileSync(src, 'utf8')

if (regionName) {
const lines = content.split(/\r?\n/)
const region = findRegion(lines, regionName)

if (region) {
content = dedent(
lines
.slice(region.start, region.end)
.filter(line => !region.regexp.test(line.trim()))
.join('\n')
)
}
}

token.content = content
} else {
token.content = `Code snippet path not found: ${src}`
token.info = ''
Expand Down Expand Up @@ -44,15 +129,23 @@ module.exports = function snippet (md, options = {}) {

const start = pos + 3
const end = state.skipSpacesBack(max, pos)
const rawPath = state.src.slice(start, end).trim().replace(/^@/, root)
const filename = rawPath.split(/{/).shift().trim()
const meta = rawPath.replace(filename, '')

/**
* raw path format: "/path/to/file.extension#region {meta}"
* where #region and {meta} are optionnal
*
* captures: ['/path/to/file.extension', 'extension', '#region', '{meta}']
*/
const rawPathRegexp = /^(.+(?:\.([a-z]+)))(?:(#[\w-]+))?(?: ?({\d(?:[,-]\d)?}))?$/

const rawPath = state.src.slice(start, end).trim().replace(/^@/, root).trim()
const [filename = '', extension = '', region = '', meta = ''] = (rawPathRegexp.exec(rawPath) || []).slice(1)

state.line = startLine + 1

const token = state.push('fence', 'code', 0)
token.info = filename.split('.').pop() + meta
token.src = path.resolve(filename)
token.info = extension + meta
token.src = path.resolve(filename) + region
token.markup = '```'
token.map = [startLine, startLine + 1]

Expand Down
23 changes: 23 additions & 0 deletions packages/docs/docs/guide/markdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,29 @@ It also supports [line highlighting](#line-highlighting-in-code-blocks):
Since the import of the code snippets will be executed before webpack compilation, you can’t use the path alias in webpack. The default value of `@` is `process.cwd()`.
:::

You can also use a [VS Code region](https://code.visualstudio.com/docs/editor/codebasics#_folding) in order to only include the corresponding part of the code file. You can provide a custom region name after a `#` following the filepath (`snippet` by default).

**Input**

``` md
<<< @/../@vuepress/markdown/__tests__/fragments/snippet-with-region.js#snippet{1}
```

**Code file**

<!--lint disable strong-marker-->

<<< @/../@vuepress/markdown/__tests__/fragments/snippet-with-region.js

<!--lint enable strong-marker-->

**Output**

<!--lint disable strong-marker-->

<<< @/../@vuepress/markdown/__tests__/fragments/snippet-with-region.js#snippet{1}

<!--lint enable strong-marker-->

## Advanced Configuration

Expand Down