Files
clawdbot/src/markdown/code-spans.ts

106 lines
2.4 KiB
TypeScript

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);
}