76 lines
2.6 KiB
JavaScript
76 lines
2.6 KiB
JavaScript
import { resolveWhitespacePosition } from '@vuepress/highlighter-helper';
|
|
const SPLIT_REGEXP = /(<[^>]+>)/;
|
|
const SPACE_REGEXP = /[\s\t]/g;
|
|
const classMap = {
|
|
' ': 'space',
|
|
'\t': 'tab',
|
|
};
|
|
const isSpace = (char) => char === ' ' || char === '\t';
|
|
const renderSpace = (text) => text.replaceAll(SPACE_REGEXP, (space) => `<span class="${classMap[space]}">${space}</span>`);
|
|
export const renderWhitespaceInLine = (node, position) => {
|
|
let snippets = node.content.split(SPLIT_REGEXP);
|
|
// match all whitespace
|
|
if (position === 'all') {
|
|
snippets = snippets.map((text) => !text || text[0] === '<' ? text : renderSpace(text));
|
|
}
|
|
// match whitespace at the beginning of the line
|
|
if (position === 'boundary') {
|
|
let has = true;
|
|
for (let i = 0; i < snippets.length; i++) {
|
|
const snippet = snippets[i];
|
|
if (snippet && snippet[0] !== '<') {
|
|
let j = 0;
|
|
while (snippet[j] && j < snippet.length) {
|
|
if (!isSpace(snippet[j])) {
|
|
has = false;
|
|
break;
|
|
}
|
|
j++;
|
|
}
|
|
snippets[i] = renderSpace(snippet.slice(0, j)) + snippet.slice(j);
|
|
}
|
|
if (!has) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// match whitespace at the end of the line
|
|
if (position === 'boundary' || position === 'trailing') {
|
|
let has = true;
|
|
for (let i = snippets.length - 1; i >= 0; i--) {
|
|
const snippet = snippets[i];
|
|
let j = snippet.length - 1;
|
|
if (snippet && snippet[j] !== '>') {
|
|
while (snippet[j] && j >= 0) {
|
|
if (!isSpace(snippet[j])) {
|
|
has = false;
|
|
break;
|
|
}
|
|
j--;
|
|
}
|
|
snippets[i] =
|
|
j === snippet.length - 1
|
|
? snippet
|
|
: j > 0
|
|
? snippet.slice(0, j) + renderSpace(snippet.slice(j))
|
|
: renderSpace(snippet);
|
|
}
|
|
if (!has) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
node.content = snippets.join('');
|
|
};
|
|
/**
|
|
* type: 'all' | 'boundary' | 'trailing'
|
|
*
|
|
* ```js :whitespace[=type]
|
|
*/
|
|
export const metaWhitespace = (parser, meta, defaultPosition) => {
|
|
const position = resolveWhitespacePosition(meta, defaultPosition);
|
|
if (position === false)
|
|
return;
|
|
parser.line((line) => renderWhitespaceInLine(line, position));
|
|
};
|