Skip to content

Commit

Permalink
feat(markdown): support including specific regions from markdown files (
Browse files Browse the repository at this point in the history
#3978)

Co-authored-by: Divyansh Singh <40380293+brc-dd@users.noreply.github.com>
  • Loading branch information
s-elo and brc-dd authored Jun 29, 2024
1 parent b694900 commit 143b1e9
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 4 deletions.
16 changes: 16 additions & 0 deletions __tests__/e2e/markdown-extensions/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,22 @@ export default config

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

## Markdown At File Region Snippet

<!--@include: ./region-include.md#snippet-->

## Markdown At File Range Region Snippet

<!--@include: ./region-include.md#range-region{3,4}-->

## Markdown At File Range Region Snippet without start

<!--@include: ./region-include.md#range-region{,2}-->

## Markdown At File Range Region Snippet without end

<!--@include: ./region-include.md#range-region{5,}-->

## Image Lazy Loading

![vitepress logo](/vitepress.png)
22 changes: 21 additions & 1 deletion __tests__/e2e/markdown-extensions/markdown-extensions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('Table of Contents', () => {
test('render toc', async () => {
const items = page.locator('#table-of-contents + nav ul li')
const count = await items.count()
expect(count).toBe(36)
expect(count).toBe(44)
})
})

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

test('support markdown region snippet', async () => {
const h2 = page.locator('#markdown-at-file-region-snippet + h2')
expect(await h2.getAttribute('id')).toBe('region-snippet')

const line = page.locator('#markdown-at-file-range-region-snippet + h2')
expect(await line.getAttribute('id')).toBe('range-region-line-2')

const lineWithoutStart = page.locator(
'#markdown-at-file-range-region-snippet-without-start + h2'
)
expect(await lineWithoutStart.getAttribute('id')).toBe(
'range-region-line-1'
)

const lineWithoutEnd = page.locator(
'#markdown-at-file-range-region-snippet-without-end + h2'
)
expect(await lineWithoutEnd.getAttribute('id')).toBe('range-region-line-3')
})

test('ignore frontmatter if range is not specified', async () => {
const p = page.locator('.vp-doc')
expect(await p.textContent()).not.toContain('title')
Expand Down
13 changes: 13 additions & 0 deletions __tests__/e2e/markdown-extensions/region-include.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

<!-- #region range-region -->

## Range Region Line 1

## Range Region Line 2

## Range Region Line 3
<!-- #endregion range-region -->

<!-- #region snippet -->
## Region Snippet
<!-- #endregion snippet -->
37 changes: 37 additions & 0 deletions docs/en/guide/markdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,43 @@ Can be created using `.foorc.json`.

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

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:

**Input**

```md
# Docs

## Basics

<!--@include: ./parts/basics.md#basic-usage{,2}-->
<!--@include: ./parts/basics.md#basic-usage{5,}-->
```

**Part file** (`parts/basics.md`)

```md
<!-- #region basic-usage -->
## Usage Line 1

## Usage Line 2

## Usage Line 3
<!-- #endregion basic-usage -->
```

**Equivalent code**

```md
# Docs

## Basics

## Usage Line 1

## Usage Line 3
```

::: warning
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.
:::
Expand Down
2 changes: 1 addition & 1 deletion src/node/markdown/plugins/snippet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function testLine(
)
}

function findRegion(lines: Array<string>, regionName: string) {
export function findRegion(lines: Array<string>, regionName: string) {
const regionRegexps = [
/^\/\/ ?#?((?:end)?region) ([\w*-]+)$/, // javascript, typescript, java
/^\/\* ?#((?:end)?region) ([\w*-]+) ?\*\/$/, // css, less, scss
Expand Down
34 changes: 32 additions & 2 deletions src/node/utils/processIncludes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import fs from 'fs-extra'
import matter from 'gray-matter'
import path from 'path'
import c from 'picocolors'
import { findRegion } from '../markdown/plugins/snippet'
import { slash } from '../shared'

export function processIncludes(
Expand All @@ -10,18 +12,37 @@ export function processIncludes(
includes: string[]
): string {
const includesRE = /<!--\s*@include:\s*(.*?)\s*-->/g
const regionRE = /(#[\w-]+)/
const rangeRE = /\{(\d*),(\d*)\}$/

return src.replace(includesRE, (m: string, m1: string) => {
if (!m1.length) return m

const range = m1.match(rangeRE)
range && (m1 = m1.slice(0, -range[0].length))
const region = m1.match(regionRE)

const hasMeta = !!(region || range)

if (hasMeta) {
const len = (region?.[0].length || 0) + (range?.[0].length || 0)
m1 = m1.slice(0, -len) // remove meta info from the include path
}

const atPresent = m1[0] === '@'

try {
const includePath = atPresent
? path.join(srcDir, m1.slice(m1[1] === '/' ? 2 : 1))
: path.join(path.dirname(file), m1)
let content = fs.readFileSync(includePath, 'utf-8')

if (region) {
const [regionName] = region
const lines = content.split(/\r?\n/)
const regionLines = findRegion(lines, regionName.slice(1))
content = lines.slice(regionLines?.start, regionLines?.end).join('\n')
}

if (range) {
const [, startLine, endLine] = range
const lines = content.split(/\r?\n/)
Expand All @@ -31,13 +52,22 @@ export function processIncludes(
endLine ? parseInt(endLine, 10) : undefined
)
.join('\n')
} else {
}

if (!hasMeta && path.extname(includePath) === '.md') {
content = matter(content).content
}

includes.push(slash(includePath))
// recursively process includes in the content
return processIncludes(srcDir, content, includePath, includes)

//
} catch (error) {
if (process.env.DEBUG) {
process.stderr.write(c.yellow(`\nInclude file not found: ${m1}`))
}

return m // silently ignore error if file is not present
}
})
Expand Down

0 comments on commit 143b1e9

Please sign in to comment.