Skip to content

Commit

Permalink
refactor: parse markdown by regex instead of ast
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk committed Jun 7, 2022
1 parent a42ee36 commit 9b98920
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 202 deletions.
5 changes: 1 addition & 4 deletions packages/vue-typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"directory": "packages/vue-typescript"
},
"devDependencies": {
"@types/markdown-it": "^12.2.3",
"@volar/pug-language-service": "0.37.2",
"typescript": "latest"
},
Expand All @@ -23,9 +22,7 @@
"@volar/typescript-faster": "0.37.2",
"@volar/vue-code-gen": "0.37.2",
"@vue/compiler-sfc": "^3.2.37",
"@vue/reactivity": "^3.2.37",
"markdown-it": "^13.0.1",
"markdown-it-ast": "^0.0.1"
"@vue/reactivity": "^3.2.37"
},
"browser": {
"./out/plugins/vue-template-pug.js": "./out/plugins/empty.js"
Expand Down
210 changes: 54 additions & 156 deletions packages/vue-typescript/src/plugins/file-md.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import { VueLanguagePlugin } from '../vueFile';
import * as MarkdownIt from 'markdown-it';
// @ts-expect-error
import * as MarkdownItAst from 'markdown-it-ast';
import { SourceMapBase, Mode } from '@volar/source-map';
import { CodeGen } from '@volar/code-gen';

Expand All @@ -13,138 +10,74 @@ export default function (): VueLanguagePlugin {

if (fileName.endsWith('.md')) {

let validTemplateBlock: [number, number] | undefined;
let validScriptBlock: [number, number] | undefined;
let validStyleBlock: [number, number] | undefined;

const scriptLines: [number, number][] = [];
const styleLines: [number, number][] = [];
const templateLines: [number, number][] = [];

const tokens = MarkdownIt().parse(content, {});
const ast = MarkdownItAst.makeAST(tokens);

for (const node of ast) {
// <script> block start tag
if (
node.nodeType === 'paragraph'
&& node.children.length
&& node.children[0].type === 'inline' && (node.children[0].content.startsWith('<script ') || node.children[0].content.startsWith('<script>'))
) {
breakTemplateBlock();
validScriptBlock = node.children[0].map;
}
// <script> block end tag
if (
validScriptBlock
&& node.nodeType === 'paragraph'
&& node.children.length
&& node.children[0].type === 'inline' && node.children[0].content.indexOf('</script>') >= 0
) {
validScriptBlock[1] = node.children[0].map[1];
scriptLines.push(validScriptBlock);
validScriptBlock = undefined;
continue;
}
if (validScriptBlock) {
continue;
}
// <style> block start tag
if (
node.nodeType === 'paragraph'
&& node.children.length
&& node.children[0].type === 'inline' && (node.children[0].content.startsWith('<style ') || node.children[0].content.startsWith('<style>'))
) {
breakTemplateBlock();
validStyleBlock = node.children[0].map;
}
// <style> block end tag
if (
validStyleBlock
&& node.nodeType === 'paragraph'
&& node.children.length
&& node.children[0].type === 'inline' && node.children[0].content.indexOf('</style>') >= 0
) {
validStyleBlock[1] = node.children[0].map[1];
styleLines.push(validStyleBlock);
validStyleBlock = undefined;
continue;
}
if (validStyleBlock) {
continue;
}
walkNode(node);
}
const _content = content;

breakTemplateBlock();
content = content
// inline code block
.replace(/```[\s\S]*?```/g, match => '```' + ' '.repeat(match.length - 6) + '```')
// inline code block
.replace(/`[\s\S]*?`/g, match => `\`${' '.repeat(match.length - 2)}\``)
// # \<script setup>
.replace(/\\<[\s\S]*?\n?/g, match => ' '.repeat(match.length));

const codeGen = new CodeGen();
const lines = content.split('\n');
const lineOffsets: number[] = [];
let lineOffset = 0;
const scriptBlockReg = /\<script[\s\S]*?\>([\s\S]*?)\<\/script\>/g;
const styleBlockReg = /\<style[\s\S]*?\>([\s\S]*?)\<\/style\>/g;

for (const line of lines) {
lineOffsets.push(lineOffset);
lineOffset += line.length + 1;
const scriptBlocks: [number, number][] = [];
const styleBlocks: [number, number][] = [];

for (const match of content.matchAll(scriptBlockReg)) {
if (match.index !== undefined) {
const matchText = match[0];
scriptBlocks.push([match.index, match.index + matchText.length]);
}
}

for (const _lines of scriptLines) {
const rangeLines = lines.slice(_lines[0], _lines[1]);
const rangeCode = rangeLines.join('\n');
const start = lineOffsets[_lines[0]];
codeGen.addCode(
rangeCode,
{
start: start,
end: start + rangeCode.length,
},
Mode.Offset,
undefined,
);
for (const match of content.matchAll(styleBlockReg)) {
if (match.index !== undefined) {
const matchText = match[0];
styleBlocks.push([match.index, match.index + matchText.length]);
}
}

for (const _lines of styleLines) {
const rangeLines = lines.slice(_lines[0], _lines[1]);
const rangeCode = rangeLines.join('\n');
const start = lineOffsets[_lines[0]];
content = content
.replace(scriptBlockReg, match => ' '.repeat(match.length))
.replace(styleBlockReg, match => ' '.repeat(match.length))
// angle bracket: <http://foo.com>
.replace(/\<\S*\:\S*\>/g, match => ' '.repeat(match.length))
// [foo](http://foo.com)
.replace(/\[[\s\S]*?\]\([\s\S]*?\)/g, match => ' '.repeat(match.length));

const codeGen = new CodeGen();

for (const block of [
...scriptBlocks,
...styleBlocks,
]) {
codeGen.addCode(
rangeCode,
_content.substring(block[0], block[1]),
{
start: start,
end: start + rangeCode.length,
start: block[0],
end: block[1],
},
Mode.Offset,
undefined,
);
codeGen.addText('\n\n');
}

if (templateLines.length) {
codeGen.addText('\n<template>\n');
for (const _lines of templateLines) {
const rangeLines = lines.slice(_lines[0], _lines[1]);
const rangeCode = rangeLines.join('\n');
const start = lineOffsets[_lines[0]];
codeGen.addCode(
rangeCode
// inline code block
.replace(/\`([\s\S]*?)\`/g, match => `\`${' '.repeat(match.length - 2)}\``)
// # \<script setup>
.replace(/\\\<([\s\S]*?)\n?/g, match => ' '.repeat(match.length))
// <http://google.com>
.replace(/\<\S*\:\S*\>/g, match => ' '.repeat(match.length))
// markdown line
.replace(/\[([\s\S]*?)\]\(([\s\S]*?)\)/g, match => ' '.repeat(match.length)),
{
start: start,
end: start + rangeCode.length,
},
Mode.Offset,
undefined,
);
codeGen.addText('\n');
}
codeGen.addText('\n</template>\n');
}
codeGen.addText('<template>\n');
codeGen.addCode(
content,
{
start: 0,
end: content.length,
},
Mode.Offset,
undefined,
);
codeGen.addText('\n');
codeGen.addText('\n</template>\n');

const sourceMap = new SourceMapBase(codeGen.getMappings());

Expand All @@ -153,42 +86,7 @@ export default function (): VueLanguagePlugin {
mapping: vueRange => sourceMap.getSourceRange(vueRange.start, vueRange.end)?.[0],
sourceMap, // for create virtual embedded vue file
};

function walkNode(node: any) {
// ignore ``` block
if (node.type === 'fence') {
breakTemplateBlock();
return false;
}
let shouldAddRange = true;
if (node.children) {
for (const child of node.children) {
shouldAddRange = shouldAddRange && walkNode(child);
}
}
if (shouldAddRange) {
const map = node.map ?? node.openNode?.map;
if (map) {
addValidTemplateBlockRange(map);
}
}
return true;
}
function breakTemplateBlock() {
if (validTemplateBlock) {
templateLines.push(validTemplateBlock);
validTemplateBlock = undefined;
}
}
function addValidTemplateBlockRange(range: [number, number]) {
if (!validTemplateBlock) {
validTemplateBlock = [range[0], range[1]];
}
else {
validTemplateBlock[1] = range[1];
}
}
}
};
}
};
}
45 changes: 3 additions & 42 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 9b98920

Please sign in to comment.