feat: support selecting line range when importing md file (#2502)

Co-authored-by: Divyansh Singh <40380293+brc-dd@users.noreply.github.com>
pull/2037/merge
烽宁 2 years ago committed by GitHub
parent 47c06bda38
commit 1ef33fe1c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,7 +1,11 @@
# Foo # Foo
This is before region
<!-- #region snippet --> <!-- #region snippet -->
## Region ## Region
this is region This is a region
<!-- #endregion snippet --> <!-- #endregion snippet -->
This is after region

@ -181,7 +181,18 @@ export default config
<!--@include: @/markdown-extensions/bar.md--> <!--@include: @/markdown-extensions/bar.md-->
## Markdown Nested File Inclusion ## Markdown Nested File Inclusion
<!--@include: ./nested-include.md--> <!--@include: ./nested-include.md-->
## Markdown File Inclusion with Range
<!--@include: ./foo.md{6,8}-->
## Markdown File Inclusion with Range without Start
<!--@include: ./foo.md{,8}-->
## Markdown File Inclusion with Range without End
<!--@include: ./foo.md{6,}-->

@ -5,6 +5,8 @@ const getClassList = async (locator: Locator) => {
return className?.split(' ').filter(Boolean) ?? [] return className?.split(' ').filter(Boolean) ?? []
} }
const trim = (str?: string | null) => str?.replace(/\u200B/g, '').trim()
beforeEach(async () => { beforeEach(async () => {
await goto('/markdown-extensions/') await goto('/markdown-extensions/')
}) })
@ -63,7 +65,7 @@ describe('Table of Contents', () => {
test('render toc', async () => { test('render toc', async () => {
const items = page.locator('#table-of-contents + nav ul li') const items = page.locator('#table-of-contents + nav ul li')
const count = await items.count() const count = await items.count()
expect(count).toBe(27) expect(count).toBe(33)
}) })
}) })
@ -161,7 +163,7 @@ describe('Line Numbers', () => {
describe('Import Code Snippets', () => { describe('Import Code Snippets', () => {
test('basic', async () => { test('basic', async () => {
const lines = page.locator('#basic-code-snippet + div code > span') const lines = page.locator('#basic-code-snippet + div code > span')
expect(await lines.count()).toBe(7) expect(await lines.count()).toBe(11)
}) })
test('specify region', async () => { test('specify region', async () => {
@ -214,7 +216,7 @@ describe('Code Groups', () => {
// blocks // blocks
const blocks = div.locator('.blocks > div') const blocks = div.locator('.blocks > div')
expect(await blocks.nth(0).locator('code > span').count()).toBe(7) expect(await blocks.nth(0).locator('code > span').count()).toBe(11)
expect(await getClassList(blocks.nth(1))).toContain('line-numbers-mode') expect(await getClassList(blocks.nth(1))).toContain('line-numbers-mode')
expect(await getClassList(blocks.nth(1))).toContain('language-ts') expect(await getClassList(blocks.nth(1))).toContain('language-ts')
expect(await blocks.nth(1).locator('code > span').count()).toBe(3) expect(await blocks.nth(1).locator('code > span').count()).toBe(3)
@ -229,12 +231,38 @@ describe('Markdown File Inclusion', () => {
const h1 = page.locator('#markdown-file-inclusion + h1') const h1 = page.locator('#markdown-file-inclusion + h1')
expect(await h1.getAttribute('id')).toBe('foo') expect(await h1.getAttribute('id')).toBe('foo')
}) })
test('render markdown using @', async () => { test('render markdown using @', async () => {
const h1 = page.locator('#markdown-at-file-inclusion + h1') const h1 = page.locator('#markdown-at-file-inclusion + h1')
expect(await h1.getAttribute('id')).toBe('bar') expect(await h1.getAttribute('id')).toBe('bar')
}) })
test('render markdown using nested inclusion', async () => { test('render markdown using nested inclusion', async () => {
const h1 = page.locator('#markdown-nested-file-inclusion + h1') const h1 = page.locator('#markdown-nested-file-inclusion + h1')
expect(await h1.getAttribute('id')).toBe('foo-1') expect(await h1.getAttribute('id')).toBe('foo-1')
}) })
test('support selecting range', async () => {
const h2 = page.locator('#markdown-file-inclusion-with-range + h2')
expect(trim(await h2.textContent())).toBe('Region')
const p = page.locator('#markdown-file-inclusion-with-range + h2 + p')
expect(trim(await p.textContent())).toBe('This is a region')
})
test('support selecting range without specifying start', async () => {
const p = page.locator(
'#markdown-file-inclusion-with-range-without-start ~ p'
)
expect(trim(await p.nth(0).textContent())).toBe('This is before region')
expect(trim(await p.nth(1).textContent())).toBe('This is a region')
})
test('support selecting range without specifying end', async () => {
const p = page.locator(
'#markdown-file-inclusion-with-range-without-end ~ p'
)
expect(trim(await p.nth(0).textContent())).toBe('This is a region')
expect(trim(await p.nth(1).textContent())).toBe('This is after region')
})
}) })

@ -738,6 +738,42 @@ Some getting started stuff.
Can be created using `.foorc.json`. Can be created using `.foorc.json`.
``` ```
It also supports selecting a line range:
**Input**
```md
# Docs
## Basics
<!--@include: ./parts/basics.md{3,}-->
```
**Part file** (`parts/basics.md`)
```md
Some getting started stuff.
### Configuration
Can be created using `.foorc.json`.
```
**Equivalent code**
```md
# Docs
## Basics
### Configuration
Can be created using `.foorc.json`.
```
The format of the selected line range can be: `{3,}`, `{,10}`, `{1,10}`
::: warning ::: 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. 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.
::: :::

@ -21,6 +21,7 @@ import { getGitTimestamp } from './utils/getGitTimestamp'
const debug = _debug('vitepress:md') const debug = _debug('vitepress:md')
const cache = new LRUCache<string, MarkdownCompileResult>({ max: 1024 }) const cache = new LRUCache<string, MarkdownCompileResult>({ max: 1024 })
const includesRE = /<!--\s*@include:\s*(.*?)\s*-->/g const includesRE = /<!--\s*@include:\s*(.*?)\s*-->/g
const rangeRE = /\{(\d*),(\d*)\}$/
export interface MarkdownCompileResult { export interface MarkdownCompileResult {
vueSrc: string vueSrc: string
@ -88,15 +89,27 @@ export async function createMarkdownToVueRenderFn(
let includes: string[] = [] let includes: string[] = []
function processIncludes(src: string): string { function processIncludes(src: string): string {
return src.replace(includesRE, (m, m1) => { return src.replace(includesRE, (m: string, m1: string) => {
if (!m1.length) return m if (!m1.length) return m
const range = m1.match(rangeRE)
range && (m1 = m1.slice(0, -range[0].length))
const atPresent = m1[0] === '@' const atPresent = m1[0] === '@'
try { try {
const includePath = atPresent const includePath = atPresent
? path.join(srcDir, m1.slice(m1[1] === '/' ? 2 : 1)) ? path.join(srcDir, m1.slice(m1[1] === '/' ? 2 : 1))
: path.join(path.dirname(fileOrig), m1) : path.join(path.dirname(fileOrig), m1)
const content = fs.readFileSync(includePath, 'utf-8') let content = fs.readFileSync(includePath, 'utf-8')
if (range) {
const [, startLine, endLine] = range
const lines = content.split(/\r?\n/)
content = lines
.slice(
startLine ? parseInt(startLine, 10) - 1 : undefined,
endLine ? parseInt(endLine, 10) : undefined
)
.join('\n')
}
includes.push(slash(includePath)) includes.push(slash(includePath))
// recursively process includes in the content // recursively process includes in the content
return processIncludes(content) return processIncludes(content)

Loading…
Cancel
Save