116 lines · 4.9 KB
import { hasConflictMarkers, splitLines } from "../git/merge3";
import type { ConflictHunk } from "../git/merge3";
import { parseConflictText } from "~/lib/conflict-blocks";
import { assembleResolution } from "./candidates";

/** A candidate may reorder and lightly edit both sides, but not balloon past them. */
const GROWTH_FACTOR = 2;
const GROWTH_SLACK = 4;
const MIN_SIGNIFICANT_CHARS = 4;
const BRACE_LANGUAGES = /\.(go|ts|tsx|js|jsx|mjs|cjs|java|kt|kts|c|h|cc|cpp|hpp|cs|swift|scala|php|json|jsonc)$/i;

function bare(line: string): string {
  return line.replace(/\r?\n$/, "").trim();
}

/** Lines that carry enough content that repeating them elsewhere is not a coincidence. */
function significant(line: string): boolean {
  const text = bare(line);
  return text.length >= MIN_SIGNIFICANT_CHARS && /[\p{L}\p{N}]/u.test(text);
}

function countLines(text: string): number {
  return splitLines(text).length;
}

/** Text outside every conflict block, i.e. what the resolution is spliced into. */
function outsideLines(content: string): Set<string> {
  const parsed = parseConflictText(content);
  if (!parsed) return new Set();
  return new Set(parsed.segments.flatMap((segment) => ("text" in segment ? splitLines(segment.text).map(bare) : [])));
}

/**
 * Bracket balance with strings, runes, raw strings and comments skipped. Not a parser, just enough to spot
 * a candidate that pasted half a declaration into the hunk.
 */
export function bracketsBalanced(text: string): boolean {
  const stack: string[] = [];
  const closer: Record<string, string> = { "(": ")", "[": "]", "{": "}" };
  let index = 0;