Skip to content

Commit 143b1e9

Browse files
s-elobrc-dd
andauthored
feat(markdown): support including specific regions from markdown files (#3978)
Co-authored-by: Divyansh Singh <[email protected]>
1 parent b694900 commit 143b1e9

File tree

6 files changed

+120
-4
lines changed

6 files changed

+120
-4
lines changed

Diff for: __tests__/e2e/markdown-extensions/index.md

+16
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,22 @@ export default config
197197

198198
<!--@include: ./foo.md{6,}-->
199199

200+
## Markdown At File Region Snippet
201+
202+
<!--@include: ./region-include.md#snippet-->
203+
204+
## Markdown At File Range Region Snippet
205+
206+
<!--@include: ./region-include.md#range-region{3,4}-->
207+
208+
## Markdown At File Range Region Snippet without start
209+
210+
<!--@include: ./region-include.md#range-region{,2}-->
211+
212+
## Markdown At File Range Region Snippet without end
213+
214+
<!--@include: ./region-include.md#range-region{5,}-->
215+
200216
## Image Lazy Loading
201217

202218
![vitepress logo](/vitepress.png)

Diff for: __tests__/e2e/markdown-extensions/markdown-extensions.test.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ describe('Table of Contents', () => {
6565
test('render toc', async () => {
6666
const items = page.locator('#table-of-contents + nav ul li')
6767
const count = await items.count()
68-
expect(count).toBe(36)
68+
expect(count).toBe(44)
6969
})
7070
})
7171

@@ -275,6 +275,26 @@ describe('Markdown File Inclusion', () => {
275275
expect(trim(await p.nth(1).textContent())).toBe('This is after region')
276276
})
277277

278+
test('support markdown region snippet', async () => {
279+
const h2 = page.locator('#markdown-at-file-region-snippet + h2')
280+
expect(await h2.getAttribute('id')).toBe('region-snippet')
281+
282+
const line = page.locator('#markdown-at-file-range-region-snippet + h2')
283+
expect(await line.getAttribute('id')).toBe('range-region-line-2')
284+
285+
const lineWithoutStart = page.locator(
286+
'#markdown-at-file-range-region-snippet-without-start + h2'
287+
)
288+
expect(await lineWithoutStart.getAttribute('id')).toBe(
289+
'range-region-line-1'
290+
)
291+
292+
const lineWithoutEnd = page.locator(
293+
'#markdown-at-file-range-region-snippet-without-end + h2'
294+
)
295+
expect(await lineWithoutEnd.getAttribute('id')).toBe('range-region-line-3')
296+
})
297+
278298
test('ignore frontmatter if range is not specified', async () => {
279299
const p = page.locator('.vp-doc')
280300
expect(await p.textContent()).not.toContain('title')

Diff for: __tests__/e2e/markdown-extensions/region-include.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
<!-- #region range-region -->
3+
4+
## Range Region Line 1
5+
6+
## Range Region Line 2
7+
8+
## Range Region Line 3
9+
<!-- #endregion range-region -->
10+
11+
<!-- #region snippet -->
12+
## Region Snippet
13+
<!-- #endregion snippet -->

Diff for: docs/en/guide/markdown.md

+37
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,43 @@ Can be created using `.foorc.json`.
834834

835835
The format of the selected line range can be: `{3,}`, `{,10}`, `{1,10}`
836836

837+
You can also use a [VS Code region](https://code.visualstudio.com/docs/editor/codebasics#_folding) to only include the corresponding part of the code file. You can provide a custom region name after a `#` following the filepath:
838+
839+
**Input**
840+
841+
```md
842+
# Docs
843+
844+
## Basics
845+
846+
<!--@include: ./parts/basics.md#basic-usage{,2}-->
847+
<!--@include: ./parts/basics.md#basic-usage{5,}-->
848+
```
849+
850+
**Part file** (`parts/basics.md`)
851+
852+
```md
853+
<!-- #region basic-usage -->
854+
## Usage Line 1
855+
856+
## Usage Line 2
857+
858+
## Usage Line 3
859+
<!-- #endregion basic-usage -->
860+
```
861+
862+
**Equivalent code**
863+
864+
```md
865+
# Docs
866+
867+
## Basics
868+
869+
## Usage Line 1
870+
871+
## Usage Line 3
872+
```
873+
837874
::: warning
838875
Note that this does not throw errors if your file is not present. Hence, when using this feature make sure that the contents are being rendered as expected.
839876
:::

Diff for: src/node/markdown/plugins/snippet.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ function testLine(
6666
)
6767
}
6868

69-
function findRegion(lines: Array<string>, regionName: string) {
69+
export function findRegion(lines: Array<string>, regionName: string) {
7070
const regionRegexps = [
7171
/^\/\/ ?#?((?:end)?region) ([\w*-]+)$/, // javascript, typescript, java
7272
/^\/\* ?#((?:end)?region) ([\w*-]+) ?\*\/$/, // css, less, scss

Diff for: src/node/utils/processIncludes.ts

+32-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import fs from 'fs-extra'
22
import matter from 'gray-matter'
33
import path from 'path'
4+
import c from 'picocolors'
5+
import { findRegion } from '../markdown/plugins/snippet'
46
import { slash } from '../shared'
57

68
export function processIncludes(
@@ -10,18 +12,37 @@ export function processIncludes(
1012
includes: string[]
1113
): string {
1214
const includesRE = /<!--\s*@include:\s*(.*?)\s*-->/g
15+
const regionRE = /(#[\w-]+)/
1316
const rangeRE = /\{(\d*),(\d*)\}$/
17+
1418
return src.replace(includesRE, (m: string, m1: string) => {
1519
if (!m1.length) return m
1620

1721
const range = m1.match(rangeRE)
18-
range && (m1 = m1.slice(0, -range[0].length))
22+
const region = m1.match(regionRE)
23+
24+
const hasMeta = !!(region || range)
25+
26+
if (hasMeta) {
27+
const len = (region?.[0].length || 0) + (range?.[0].length || 0)
28+
m1 = m1.slice(0, -len) // remove meta info from the include path
29+
}
30+
1931
const atPresent = m1[0] === '@'
32+
2033
try {
2134
const includePath = atPresent
2235
? path.join(srcDir, m1.slice(m1[1] === '/' ? 2 : 1))
2336
: path.join(path.dirname(file), m1)
2437
let content = fs.readFileSync(includePath, 'utf-8')
38+
39+
if (region) {
40+
const [regionName] = region
41+
const lines = content.split(/\r?\n/)
42+
const regionLines = findRegion(lines, regionName.slice(1))
43+
content = lines.slice(regionLines?.start, regionLines?.end).join('\n')
44+
}
45+
2546
if (range) {
2647
const [, startLine, endLine] = range
2748
const lines = content.split(/\r?\n/)
@@ -31,13 +52,22 @@ export function processIncludes(
3152
endLine ? parseInt(endLine, 10) : undefined
3253
)
3354
.join('\n')
34-
} else {
55+
}
56+
57+
if (!hasMeta && path.extname(includePath) === '.md') {
3558
content = matter(content).content
3659
}
60+
3761
includes.push(slash(includePath))
3862
// recursively process includes in the content
3963
return processIncludes(srcDir, content, includePath, includes)
64+
65+
//
4066
} catch (error) {
67+
if (process.env.DEBUG) {
68+
process.stderr.write(c.yellow(`\nInclude file not found: ${m1}`))
69+
}
70+
4171
return m // silently ignore error if file is not present
4272
}
4373
})

0 commit comments

Comments
 (0)