function splitLines(text: string): string[] {
return text.match(/[^\n]*\n|[^\n]+$/g) ?? [];
}
/** One parsed piece of a conflicted file: verbatim text, or the index of a conflict block. */
export type Segment = { text: string } | { block: number };
export type ParsedBlock = { ours: string; theirs: string };
const START = /^<{7}(?: |$)/;
const BASE = /^\|{7}(?: |$)/;
const MIDDLE = /^={7}$/;
const END = /^>{7}(?: |$)/;
function stripEol(line: string): string {
return line.replace(/\r?\n$/, "");
}
/** Splits merged marker text into verbatim text and conflict blocks; null when the markers are malformed. */
export function parseConflictText(text: string): { segments: Segment[]; blocks: ParsedBlock[] } | null {
const segments: Segment[] = [];
const blocks: ParsedBlock[] = [];
let state: "outside" | "ours" | "base" | "theirs" = "outside";
let outside: string[] = [];
let ours: string[] = [];
let theirs: string[] = [];
for (const line of splitLines(text)) {
const bare = stripEol(line);
if (state === "outside") {
if (START.test(bare)) {
if (outside.length) segments.push({ text: outside.join("") });
outside = [];
ours = [];
theirs = [];
state = "ours";
} else if (BASE.test(bare) || MIDDLE.test(bare) || END.test(bare)) return null;
else outside.push(line);
} else if (state === "ours" || state === "base") {
if (BASE.test(bare) && state === "ours") state = "base";
else if (MIDDLE.test(bare)) state = "theirs";
else if (START.test(bare) || END.test(bare) || BASE.test(bare)) return null;