import { parseFenceSpans, type FenceSpan } from "./fences.js"; export type InlineCodeState = { open: boolean; ticks: number; }; export function createInlineCodeState(): InlineCodeState { return { open: false, ticks: 0 }; } type InlineCodeSpansResult = { spans: Array<[number, number]>; state: InlineCodeState; }; export type CodeSpanIndex = { inlineState: InlineCodeState; isInside: (index: number) => boolean; }; export function buildCodeSpanIndex(text: string, inlineState?: InlineCodeState): CodeSpanIndex { const fenceSpans = parseFenceSpans(text); const startState = inlineState ? { open: inlineState.open, ticks: inlineState.ticks } : createInlineCodeState(); const { spans: inlineSpans, state: nextInlineState } = parseInlineCodeSpans( text, fenceSpans, startState, ); return { inlineState: nextInlineState, isInside: (index: number) => isInsideFenceSpan(index, fenceSpans) || isInsideInlineSpan(index, inlineSpans), }; } function parseInlineCodeSpans( text: string, fenceSpans: FenceSpan[], initialState: InlineCodeState, ): InlineCodeSpansResult { const spans: Array<[number, number]> = []; let open = initialState.open; let ticks = initialState.ticks; let openStart = open ? 0 : -1; let i = 0; while (i < text.length) { const fence = findFenceSpanAtInclusive(fenceSpans, i); if (fence) { i = fence.end; continue; } if (text[i] !== "`") { i += 1; continue; } const runStart = i; let runLength = 0; while (i < text.length && text[i] === "`") { runLength += 1; i += 1; } if (!open) { open = true; ticks = runLength; openStart = runStart; continue; } if (runLength === ticks) { spans.push([openStart, i]); open = false; ticks = 0; openStart = -1; } } if (open) { spans.push([openStart, text.length]); } return { spans, state: { open, ticks }, }; } function findFenceSpanAtInclusive(spans: FenceSpan[], index: number): FenceSpan | undefined { return spans.find((span) => index >= span.start && index < span.end); } function isInsideFenceSpan(index: number, spans: FenceSpan[]): boolean { return spans.some((span) => index >= span.start && index < span.end); } function isInsideInlineSpan(index: number, spans: Array<[number, number]>): boolean { return spans.some(([start, end]) => index >= start && index < end); }