Skip to content

Commit 1ef33fe

Browse files
zonemeenbrc-dd
andauthored
feat: support selecting line range when importing md file (#2502)
Co-authored-by: Divyansh Singh <[email protected]>
1 parent 47c06bd commit 1ef33fe

File tree

5 files changed

+100
-8
lines changed

5 files changed

+100
-8
lines changed
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
# Foo
22

3+
This is before region
4+
35
<!-- #region snippet -->
46
## Region
57

6-
this is region
7-
<!-- #endregion snippet -->
8+
This is a region
9+
<!-- #endregion snippet -->
10+
11+
This is after region

__tests__/e2e/markdown-extensions/index.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,18 @@ export default config
181181

182182
<!--@include: @/markdown-extensions/bar.md-->
183183

184-
185184
## Markdown Nested File Inclusion
186185

187186
<!--@include: ./nested-include.md-->
187+
188+
## Markdown File Inclusion with Range
189+
190+
<!--@include: ./foo.md{6,8}-->
191+
192+
## Markdown File Inclusion with Range without Start
193+
194+
<!--@include: ./foo.md{,8}-->
195+
196+
## Markdown File Inclusion with Range without End
197+
198+
<!--@include: ./foo.md{6,}-->

__tests__/e2e/markdown-extensions/markdown-extensions.test.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ const getClassList = async (locator: Locator) => {
55
return className?.split(' ').filter(Boolean) ?? []
66
}
77

8+
const trim = (str?: string | null) => str?.replace(/\u200B/g, '').trim()
9+
810
beforeEach(async () => {
911
await goto('/markdown-extensions/')
1012
})
@@ -63,7 +65,7 @@ describe('Table of Contents', () => {
6365
test('render toc', async () => {
6466
const items = page.locator('#table-of-contents + nav ul li')
6567
const count = await items.count()
66-
expect(count).toBe(27)
68+
expect(count).toBe(33)
6769
})
6870
})
6971

@@ -161,7 +163,7 @@ describe('Line Numbers', () => {
161163
describe('Import Code Snippets', () => {
162164
test('basic', async () => {
163165
const lines = page.locator('#basic-code-snippet + div code > span')
164-
expect(await lines.count()).toBe(7)
166+
expect(await lines.count()).toBe(11)
165167
})
166168

167169
test('specify region', async () => {
@@ -214,7 +216,7 @@ describe('Code Groups', () => {
214216

215217
// blocks
216218
const blocks = div.locator('.blocks > div')
217-
expect(await blocks.nth(0).locator('code > span').count()).toBe(7)
219+
expect(await blocks.nth(0).locator('code > span').count()).toBe(11)
218220
expect(await getClassList(blocks.nth(1))).toContain('line-numbers-mode')
219221
expect(await getClassList(blocks.nth(1))).toContain('language-ts')
220222
expect(await blocks.nth(1).locator('code > span').count()).toBe(3)
@@ -229,12 +231,38 @@ describe('Markdown File Inclusion', () => {
229231
const h1 = page.locator('#markdown-file-inclusion + h1')
230232
expect(await h1.getAttribute('id')).toBe('foo')
231233
})
234+
232235
test('render markdown using @', async () => {
233236
const h1 = page.locator('#markdown-at-file-inclusion + h1')
234237
expect(await h1.getAttribute('id')).toBe('bar')
235238
})
239+
236240
test('render markdown using nested inclusion', async () => {
237241
const h1 = page.locator('#markdown-nested-file-inclusion + h1')
238242
expect(await h1.getAttribute('id')).toBe('foo-1')
239243
})
244+
245+
test('support selecting range', async () => {
246+
const h2 = page.locator('#markdown-file-inclusion-with-range + h2')
247+
expect(trim(await h2.textContent())).toBe('Region')
248+
249+
const p = page.locator('#markdown-file-inclusion-with-range + h2 + p')
250+
expect(trim(await p.textContent())).toBe('This is a region')
251+
})
252+
253+
test('support selecting range without specifying start', async () => {
254+
const p = page.locator(
255+
'#markdown-file-inclusion-with-range-without-start ~ p'
256+
)
257+
expect(trim(await p.nth(0).textContent())).toBe('This is before region')
258+
expect(trim(await p.nth(1).textContent())).toBe('This is a region')
259+
})
260+
261+
test('support selecting range without specifying end', async () => {
262+
const p = page.locator(
263+
'#markdown-file-inclusion-with-range-without-end ~ p'
264+
)
265+
expect(trim(await p.nth(0).textContent())).toBe('This is a region')
266+
expect(trim(await p.nth(1).textContent())).toBe('This is after region')
267+
})
240268
})

docs/guide/markdown.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,42 @@ Some getting started stuff.
738738
Can be created using `.foorc.json`.
739739
```
740740

741+
It also supports selecting a line range:
742+
743+
**Input**
744+
745+
```md
746+
# Docs
747+
748+
## Basics
749+
750+
<!--@include: ./parts/basics.md{3,}-->
751+
```
752+
753+
**Part file** (`parts/basics.md`)
754+
755+
```md
756+
Some getting started stuff.
757+
758+
### Configuration
759+
760+
Can be created using `.foorc.json`.
761+
```
762+
763+
**Equivalent code**
764+
765+
```md
766+
# Docs
767+
768+
## Basics
769+
770+
### Configuration
771+
772+
Can be created using `.foorc.json`.
773+
```
774+
775+
The format of the selected line range can be: `{3,}`, `{,10}`, `{1,10}`
776+
741777
::: warning
742778
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.
743779
:::

src/node/markdownToVue.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { getGitTimestamp } from './utils/getGitTimestamp'
2121
const debug = _debug('vitepress:md')
2222
const cache = new LRUCache<string, MarkdownCompileResult>({ max: 1024 })
2323
const includesRE = /<!--\s*@include:\s*(.*?)\s*-->/g
24+
const rangeRE = /\{(\d*),(\d*)\}$/
2425

2526
export interface MarkdownCompileResult {
2627
vueSrc: string
@@ -88,15 +89,27 @@ export async function createMarkdownToVueRenderFn(
8889
let includes: string[] = []
8990

9091
function processIncludes(src: string): string {
91-
return src.replace(includesRE, (m, m1) => {
92+
return src.replace(includesRE, (m: string, m1: string) => {
9293
if (!m1.length) return m
9394

95+
const range = m1.match(rangeRE)
96+
range && (m1 = m1.slice(0, -range[0].length))
9497
const atPresent = m1[0] === '@'
9598
try {
9699
const includePath = atPresent
97100
? path.join(srcDir, m1.slice(m1[1] === '/' ? 2 : 1))
98101
: path.join(path.dirname(fileOrig), m1)
99-
const content = fs.readFileSync(includePath, 'utf-8')
102+
let content = fs.readFileSync(includePath, 'utf-8')
103+
if (range) {
104+
const [, startLine, endLine] = range
105+
const lines = content.split(/\r?\n/)
106+
content = lines
107+
.slice(
108+
startLine ? parseInt(startLine, 10) - 1 : undefined,
109+
endLine ? parseInt(endLine, 10) : undefined
110+
)
111+
.join('\n')
112+
}
100113
includes.push(slash(includePath))
101114
// recursively process includes in the content
102115
return processIncludes(content)

0 commit comments

Comments
 (0)