From 099c01703c8d6f56865bccde488f991a7bfb742f Mon Sep 17 00:00:00 2001 From: mysticmind Date: Thu, 11 Feb 2021 23:51:06 +0530 Subject: [PATCH 1/6] feat: import code snippet with region (#237) --- src/node/markdown/plugins/snippet.ts | 147 +++++++++++++++++++++++++-- 1 file changed, 139 insertions(+), 8 deletions(-) diff --git a/src/node/markdown/plugins/snippet.ts b/src/node/markdown/plugins/snippet.ts index fd56fb564f9e..a2c38ffb1548 100644 --- a/src/node/markdown/plugins/snippet.ts +++ b/src/node/markdown/plugins/snippet.ts @@ -1,7 +1,83 @@ import fs from 'fs' +import path from 'path' import MarkdownIt from 'markdown-it' import { RuleBlock } from 'markdown-it/lib/parser_block' +function dedent(text: string) { + 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: string, + regexp: RegExp, + regionName: string, + end: boolean = 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: Array, regionName: string) { + const regionRegexps = [ + /^\/\/ ?#?((?:end)?region) ([\w*-]+)$/, // javascript, typescript, java + /^\/\* ?#((?:end)?region) ([\w*-]+) ?\*\/$/, // css, less, scss + /^#pragma ((?:end)?region) ([\w*-]+)$/, // C, C++ + /^$/, // 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 +} + export const snippetPlugin = (md: MarkdownIt, root: string) => { const parser: RuleBlock = (state, startLine, endLine, silent) => { const CH = '<'.charCodeAt(0) @@ -24,23 +100,78 @@ export const snippetPlugin = (md: MarkdownIt, root: string) => { 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 content = fs.existsSync(filename) - ? fs.readFileSync(filename).toString() - : 'Not found: ' + filename - const meta = rawPath.replace(filename, '') + + /** + * raw path format: "/path/to/file.extension#region {meta}" + * where #region and {meta} are optional + * + * 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.content = content + token.info = extension + meta + + // @ts-ignore + token.src = path.resolve(filename) + region token.markup = '```' token.map = [startLine, startLine + 1] return true } + const fence = md.renderer.rules.fence! + + md.renderer.rules.fence = (...args) => { + const [tokens, idx, , { loader }] = args + const token = tokens[idx] + // @ts-ignore + const tokenSrc = token.src + const [src, regionName] = tokenSrc ? tokenSrc.split('#') : [''] + + if (src) { + if (loader) { + loader.addDependency(src) + } + const isAFile = fs.lstatSync(src).isFile() + if (fs.existsSync(src) && isAFile) { + 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: string) => !region.regexp.test(line.trim())) + .join('\n') + ) + } + } + + token.content = content + } else { + token.content = isAFile + ? `Code snippet path not found: ${src}` + : `Invalid code snippet option` + token.info = '' + } + } + return fence(...args) + } + md.block.ruler.before('fence', 'snippet', parser) } From 17b5334bf5cd284e24a2db14a061bb10cf424566 Mon Sep 17 00:00:00 2001 From: mysticmind Date: Fri, 12 Feb 2021 15:42:16 +0530 Subject: [PATCH 2/6] Add docs for import code snippets --- docs/guide/markdown.md | 57 ++++++++++++++++++++++++++++ docs/snippets/snippet-with-region.js | 7 ++++ docs/snippets/snippet.js | 3 ++ 3 files changed, 67 insertions(+) create mode 100644 docs/snippets/snippet-with-region.js create mode 100644 docs/snippets/snippet.js diff --git a/docs/guide/markdown.md b/docs/guide/markdown.md index 8245dbb6b77f..274eb61c63aa 100644 --- a/docs/guide/markdown.md +++ b/docs/guide/markdown.md @@ -324,6 +324,63 @@ module.exports = { } +## Import Code Snippets + +You can import code snippets from existing files via following syntax: + +``` md +<<< ./filepath +``` + +It also supports [line highlighting](#line-highlighting-in-code-blocks): + +``` md +<<< ./filepath{highlightLines} +``` + +**Input** + +``` md +<<< ./snippets/snippet.js{2} +``` + +**Output** + + + +<<< ./snippets/snippet.js{2} + + + +::: tip +The value of `.` corresponds to `process.cwd()`. +::: + +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 (`snippet` by default): + +**Input** + +``` md +<<< ./snippets/snippet-with-region.js{1} +``` + +**Code file** + + + +<<< ./snippets/snippet-with-region.js + + + +**Output** + + + +<<< ./snippets/snippet-with-region.js#snippet{1} + + + + ## Advanced Configuration VitePress uses [markdown-it](https://github.com/markdown-it/markdown-it) as the Markdown renderer. A lot of the extensions above are implemented via custom plugins. You can further customize the `markdown-it` instance using the `markdown` option in `.vitepress/config.js`: diff --git a/docs/snippets/snippet-with-region.js b/docs/snippets/snippet-with-region.js new file mode 100644 index 000000000000..9c7faaebca19 --- /dev/null +++ b/docs/snippets/snippet-with-region.js @@ -0,0 +1,7 @@ +// #region snippet +function foo() { + // .. +} +// #endregion snippet + +export default foo diff --git a/docs/snippets/snippet.js b/docs/snippets/snippet.js new file mode 100644 index 000000000000..575039d1ec15 --- /dev/null +++ b/docs/snippets/snippet.js @@ -0,0 +1,3 @@ +export default function () { + // .. +} From 17298bc05efbd91c553b1564ca61b172821d1cdf Mon Sep 17 00:00:00 2001 From: mysticmind Date: Fri, 12 Feb 2021 15:53:22 +0530 Subject: [PATCH 3/6] Move code snippets under __tests__/node/markdown/fragments --- .../node/markdown/fragments}/snippet-with-region.js | 0 .../node/markdown/fragments}/snippet.js | 0 docs/guide/markdown.md | 10 +++++----- 3 files changed, 5 insertions(+), 5 deletions(-) rename {docs/snippets => __tests__/node/markdown/fragments}/snippet-with-region.js (100%) rename {docs/snippets => __tests__/node/markdown/fragments}/snippet.js (100%) diff --git a/docs/snippets/snippet-with-region.js b/__tests__/node/markdown/fragments/snippet-with-region.js similarity index 100% rename from docs/snippets/snippet-with-region.js rename to __tests__/node/markdown/fragments/snippet-with-region.js diff --git a/docs/snippets/snippet.js b/__tests__/node/markdown/fragments/snippet.js similarity index 100% rename from docs/snippets/snippet.js rename to __tests__/node/markdown/fragments/snippet.js diff --git a/docs/guide/markdown.md b/docs/guide/markdown.md index 274eb61c63aa..0fc3728c0fa3 100644 --- a/docs/guide/markdown.md +++ b/docs/guide/markdown.md @@ -341,14 +341,14 @@ It also supports [line highlighting](#line-highlighting-in-code-blocks): **Input** ``` md -<<< ./snippets/snippet.js{2} +<<< ./../__tests__/node/markdown/fragments/snippet.js{2} ``` **Output** -<<< ./snippets/snippet.js{2} +<<< ./../__tests__/node/markdown/fragments/snippet.js{2} @@ -361,14 +361,14 @@ You can also use a [VS Code region](https://code.visualstudio.com/docs/editor/co **Input** ``` md -<<< ./snippets/snippet-with-region.js{1} +<<< ./../__tests__/node/markdown/fragments/snippet-with-region.js{1} ``` **Code file** -<<< ./snippets/snippet-with-region.js +<<< ./../__tests__/node/markdown/fragments/snippet-with-region.js @@ -376,7 +376,7 @@ You can also use a [VS Code region](https://code.visualstudio.com/docs/editor/co -<<< ./snippets/snippet-with-region.js#snippet{1} +<<< ./../__tests__/node/markdown/fragments/snippet-with-region.js#snippet{1} From 43e0785a811fda462d7924ed850b80235f6d4d9e Mon Sep 17 00:00:00 2001 From: mysticmind Date: Fri, 12 Feb 2021 20:45:56 +0530 Subject: [PATCH 4/6] Fix paths for import code snippets docs --- docs/guide/markdown.md | 16 ++++++++-------- .../snippets}/snippet-with-region.js | 0 .../fragments => docs/snippets}/snippet.js | 0 3 files changed, 8 insertions(+), 8 deletions(-) rename {__tests__/node/markdown/fragments => docs/snippets}/snippet-with-region.js (100%) rename {__tests__/node/markdown/fragments => docs/snippets}/snippet.js (100%) diff --git a/docs/guide/markdown.md b/docs/guide/markdown.md index 0fc3728c0fa3..57c7fc23c074 100644 --- a/docs/guide/markdown.md +++ b/docs/guide/markdown.md @@ -329,31 +329,31 @@ module.exports = { You can import code snippets from existing files via following syntax: ``` md -<<< ./filepath +<<< @/filepath ``` It also supports [line highlighting](#line-highlighting-in-code-blocks): ``` md -<<< ./filepath{highlightLines} +<<< @/filepath{highlightLines} ``` **Input** ``` md -<<< ./../__tests__/node/markdown/fragments/snippet.js{2} +<<< @/snippets/snippet.js{2} ``` **Output** -<<< ./../__tests__/node/markdown/fragments/snippet.js{2} +<<< @/snippets/snippet.js{2} ::: tip -The value of `.` corresponds to `process.cwd()`. +The value of `@` corresponds to `process.cwd()`. ::: 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 (`snippet` by default): @@ -361,14 +361,14 @@ You can also use a [VS Code region](https://code.visualstudio.com/docs/editor/co **Input** ``` md -<<< ./../__tests__/node/markdown/fragments/snippet-with-region.js{1} +<<< @/snippets/snippet-with-region.js{1} ``` **Code file** -<<< ./../__tests__/node/markdown/fragments/snippet-with-region.js +<<< @/snippets/snippet-with-region.js @@ -376,7 +376,7 @@ You can also use a [VS Code region](https://code.visualstudio.com/docs/editor/co -<<< ./../__tests__/node/markdown/fragments/snippet-with-region.js#snippet{1} +<<< @/snippets/snippet-with-region.js#snippet{1} diff --git a/__tests__/node/markdown/fragments/snippet-with-region.js b/docs/snippets/snippet-with-region.js similarity index 100% rename from __tests__/node/markdown/fragments/snippet-with-region.js rename to docs/snippets/snippet-with-region.js diff --git a/__tests__/node/markdown/fragments/snippet.js b/docs/snippets/snippet.js similarity index 100% rename from __tests__/node/markdown/fragments/snippet.js rename to docs/snippets/snippet.js From 6cba83d123293738e1594b3519f17057aff4ec50 Mon Sep 17 00:00:00 2001 From: mysticmind Date: Fri, 12 Feb 2021 20:59:14 +0530 Subject: [PATCH 5/6] Add code file for the first import code snippet example --- docs/guide/markdown.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/guide/markdown.md b/docs/guide/markdown.md index 57c7fc23c074..b2fe1827cbea 100644 --- a/docs/guide/markdown.md +++ b/docs/guide/markdown.md @@ -344,6 +344,14 @@ It also supports [line highlighting](#line-highlighting-in-code-blocks): <<< @/snippets/snippet.js{2} ``` +**Code file** + + + +<<< @/snippets/snippet.js + + + **Output** From 0d59057d54be1fba64673fed2cd68d6c2d4ecfa0 Mon Sep 17 00:00:00 2001 From: Kia King Ishii Date: Thu, 8 Apr 2021 18:05:41 +0900 Subject: [PATCH 6/6] style: adjust code style a bit --- docs/guide/markdown.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/guide/markdown.md b/docs/guide/markdown.md index b2fe1827cbea..9134d4ba7d27 100644 --- a/docs/guide/markdown.md +++ b/docs/guide/markdown.md @@ -328,19 +328,19 @@ module.exports = { You can import code snippets from existing files via following syntax: -``` md +```md <<< @/filepath ``` It also supports [line highlighting](#line-highlighting-in-code-blocks): -``` md +```md <<< @/filepath{highlightLines} ``` **Input** -``` md +```md <<< @/snippets/snippet.js{2} ``` @@ -368,7 +368,7 @@ You can also use a [VS Code region](https://code.visualstudio.com/docs/editor/co **Input** -``` md +```md <<< @/snippets/snippet-with-region.js{1} ``` @@ -388,7 +388,6 @@ You can also use a [VS Code region](https://code.visualstudio.com/docs/editor/co - ## Advanced Configuration VitePress uses [markdown-it](https://github.com/markdown-it/markdown-it) as the Markdown renderer. A lot of the extensions above are implemented via custom plugins. You can further customize the `markdown-it` instance using the `markdown` option in `.vitepress/config.js`: