UNPKG

@tanstack/highlight

Version:

Tiny class-based syntax highlighting for documentation.

147 lines (146 loc) • 5.11 kB
import { offsetRanges } from './patterns.js'; const rawTextClosing = { script: /<\/script(?=[\s/>])/gi, style: /<\/style(?=[\s/>])/gi, }; export function collectMarkupRanges(code, context, mode = 'html') { const ranges = []; const regions = []; let index = 0; while ((index = code.indexOf('<', index)) >= 0) { if (code.startsWith('<!--', index)) { const close = code.indexOf('-->', index + 4); const end = close < 0 ? code.length : close + 3; ranges.push({ start: index, end, className: 'comment' }); index = end; continue; } if (mode === 'ejs' && code.startsWith('<%', index)) { const close = code.indexOf('%>', index + 2); const end = close < 0 ? code.length : close; regions.push({ start: index + 2, end, lang: 'js' }); index = close < 0 ? code.length : close + 2; continue; } const closing = code[index + 1] === '/'; const nameStart = index + (closing ? 2 : 1); const nameMatch = /^[A-Za-z][\w:.-]*/.exec(code.slice(nameStart)); if (!nameMatch) { index++; continue; } const tagEnd = findTagEnd(code, nameStart + nameMatch[0].length); if (tagEnd < 0) { index++; continue; } const tagName = nameMatch[0].toLowerCase(); ranges.push({ start: nameStart, end: nameStart + nameMatch[0].length, className: 'tag', }); if (!closing) collectAttributes(code, nameStart + nameMatch[0].length, tagEnd, ranges); if (!closing && (tagName === 'script' || tagName === 'style')) { const rawTextClose = rawTextClosing[tagName]; rawTextClose.lastIndex = tagEnd + 1; const contentEnd = rawTextClose.exec(code)?.index ?? code.length; const lang = getEmbeddedLanguage(tagName, code.slice(index, tagEnd + 1)); regions.push({ start: tagEnd + 1, end: contentEnd, lang }); index = contentEnd; continue; } index = tagEnd + 1; } if (mode === 'vue') collectDelimitedRegions(code, '{{', '}}', 'js', regions); if (mode === 'svelte') collectBraceRegions(code, regions); for (const region of regions) { if (region.start >= region.end || !context.hasLanguage(region.lang)) continue; ranges.push(...offsetRanges(context.tokenize(code.slice(region.start, region.end), region.lang), region.start)); } return ranges; } function collectAttributes(code, start, end, ranges) { const source = code.slice(start, end); const attributes = /\s([:@A-Za-z_][\w:.-]*)(?=\s*(?:=|\/?>))/g; let match; while ((match = attributes.exec(source))) { const offset = match[0].indexOf(match[1]); const attributeStart = start + match.index + offset; ranges.push({ start: attributeStart, end: attributeStart + match[1].length, className: 'attr', }); } let index = start; while (index < end) { const quote = code[index]; if (quote !== '"' && quote !== "'") { index++; continue; } const close = findQuoteEnd(code, index, quote, end); ranges.push({ start: index, end: close, className: 'string' }); index = close; } } function getEmbeddedLanguage(tag, openingTag) { if (tag === 'style') return 'css'; const lang = /\blang\s*=\s*["']?([\w-]+)/i.exec(openingTag)?.[1]; return lang === 'ts' || lang === 'typescript' ? 'ts' : 'js'; } function collectDelimitedRegions(code, open, close, lang, regions) { let index = 0; while ((index = code.indexOf(open, index)) >= 0) { const end = code.indexOf(close, index + open.length); if (end < 0) return; regions.push({ start: index + open.length, end, lang }); index = end + close.length; } } function collectBraceRegions(code, regions) { let index = 0; while (index < code.length) { if (code[index] !== '{' || code[index + 1] === '#') { index++; continue; } const end = code.indexOf('}', index + 1); if (end < 0) return; regions.push({ start: index + 1, end, lang: 'js' }); index = end + 1; } } function findTagEnd(code, start) { let index = start; while (index < code.length) { if (code[index] === '"' || code[index] === "'") { index = findQuoteEnd(code, index, code[index], code.length); } else if (code[index] === '>') return index; else index++; } return -1; } function findQuoteEnd(code, start, quote, limit) { let index = start + 1; while (index < limit) { if (code[index] === '\\') index += 2; else if (code[index] === quote) return index + 1; else index++; } return limit; }