import { autocorrectReplacementLines } from "./autocorrect-replacement-lines" import { restoreLeadingIndent, stripInsertAnchorEcho, stripInsertBeforeEcho, stripInsertBoundaryEcho, stripRangeBoundaryEcho, toNewLines, } from "./edit-text-normalization" import { parseLineRef, validateLineRef } from "./validation" interface EditApplyOptions { skipValidation?: boolean } function shouldValidate(options?: EditApplyOptions): boolean { return options?.skipValidation !== true } export function applySetLine( lines: string[], anchor: string, newText: string | string[], options?: EditApplyOptions ): string[] { if (shouldValidate(options)) validateLineRef(lines, anchor) const { line } = parseLineRef(anchor) const result = [...lines] const originalLine = lines[line - 1] ?? "" const corrected = autocorrectReplacementLines([originalLine], toNewLines(newText)) const replacement = corrected.map((entry, idx) => { if (idx !== 0) return entry return restoreLeadingIndent(originalLine, entry) }) result.splice(line - 1, 1, ...replacement) return result } export function applyReplaceLines( lines: string[], startAnchor: string, endAnchor: string, newText: string | string[], options?: EditApplyOptions ): string[] { if (shouldValidate(options)) { validateLineRef(lines, startAnchor) validateLineRef(lines, endAnchor) } const { line: startLine } = parseLineRef(startAnchor) const { line: endLine } = parseLineRef(endAnchor) if (startLine > endLine) { throw new Error( `Invalid range: start line ${startLine} cannot be greater than end line ${endLine}` ) } const result = [...lines] const originalRange = lines.slice(startLine - 1, endLine) const stripped = stripRangeBoundaryEcho(lines, startLine, endLine, toNewLines(newText)) const corrected = autocorrectReplacementLines(originalRange, stripped) const restored = corrected.map((entry, idx) => { if (idx !== 0) return entry return restoreLeadingIndent(lines[startLine - 1], entry) }) result.splice(startLine - 1, endLine - startLine + 1, ...restored) return result } export function applyInsertAfter( lines: string[], anchor: string, text: string | string[], options?: EditApplyOptions ): string[] { if (shouldValidate(options)) validateLineRef(lines, anchor) const { line } = parseLineRef(anchor) const result = [...lines] const newLines = stripInsertAnchorEcho(lines[line - 1], toNewLines(text)) if (newLines.length === 0) { throw new Error(`insert_after requires non-empty text for ${anchor}`) } result.splice(line, 0, ...newLines) return result } export function applyInsertBefore( lines: string[], anchor: string, text: string | string[], options?: EditApplyOptions ): string[] { if (shouldValidate(options)) validateLineRef(lines, anchor) const { line } = parseLineRef(anchor) const result = [...lines] const newLines = stripInsertBeforeEcho(lines[line - 1], toNewLines(text)) if (newLines.length === 0) { throw new Error(`insert_before requires non-empty text for ${anchor}`) } result.splice(line - 1, 0, ...newLines) return result } export function applyInsertBetween( lines: string[], afterAnchor: string, beforeAnchor: string, text: string | string[], options?: EditApplyOptions ): string[] { if (shouldValidate(options)) { validateLineRef(lines, afterAnchor) validateLineRef(lines, beforeAnchor) } const { line: afterLine } = parseLineRef(afterAnchor) const { line: beforeLine } = parseLineRef(beforeAnchor) if (beforeLine <= afterLine) { throw new Error(`insert_between requires after_line (${afterLine}) < before_line (${beforeLine})`) } const result = [...lines] const newLines = stripInsertBoundaryEcho(lines[afterLine - 1], lines[beforeLine - 1], toNewLines(text)) if (newLines.length === 0) { throw new Error(`insert_between requires non-empty text for ${afterAnchor}..${beforeAnchor}`) } result.splice(beforeLine - 1, 0, ...newLines) return result } export function applyAppend(lines: string[], text: string | string[]): string[] { const normalized = toNewLines(text) if (normalized.length === 0) { throw new Error("append requires non-empty text") } return [...lines, ...normalized] } export function applyPrepend(lines: string[], text: string | string[]): string[] { const normalized = toNewLines(text) if (normalized.length === 0) { throw new Error("prepend requires non-empty text") } return [...normalized, ...lines] } export function applyReplace(content: string, oldText: string, newText: string | string[]): string { if (!content.includes(oldText)) { throw new Error(`Text not found: "${oldText}"`) } const replacement = Array.isArray(newText) ? newText.join("\n") : newText return content.replaceAll(oldText, replacement) }