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) => `${space}`); 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)); };