import type { TextDocument } from "./index.ts"; export interface HtmlRegion { start: number; end: number; } /** * Byte ranges of the markup inside each `view { }` block. * * This is a tolerant scanner rather than the parser: completion fires while * the document is being typed, which is exactly when it does not parse. */ export function viewRegions(text: string): HtmlRegion[] { const regions: HtmlRegion[] = []; const pattern = /\bview\s*\{/g; let match: RegExpExecArray | null; while ((match = pattern.exec(text))) { const bodyStart = match.index + match[0].length; const end = matchingBrace(text, bodyStart); regions.push({ start: bodyStart, end }); pattern.lastIndex = end; } return regions; } /** * Offset of the brace closing the block that starts at `from`, or the end of * the text when it is never closed (an unterminated block is normal mid-edit). * * Quotes are only tracked inside a tag, never in text content: `
it's
` * would otherwise open a string that never closes and swallow the rest of the * file. */ function matchingBrace(text: string, from: number): number { let depth = 1; let inTag = false; let quote: string | null = null; for (let index = from; index < text.length; index += 1) { const char = text[index]!; if (quote) { if (char === quote) quote = null; continue; } if (inTag && (char === '"' || char === "'")) { quote = char; continue; } if (char === "<") inTag = true; else if (char === ">") inTag = false; else if (char === "{") depth += 1; else if (char === "}") { depth -= 1; if (depth === 0) return index; } } return text.length; } /** * A parallel document containing only the markup. * * Everything outside a view block becomes whitespace of the same length, and * newlines are preserved, so an offset in the source is the same offset here. * That removes the need for a mapping table entirely. */ export function virtualHtmlDocument(document: TextDocument): { uri: string; languageId: "html"; text: string; } { const source = document.text; const keep = new Array