c5c7ba4eed
- error-classifier: pre-compile default retry pattern regex - think-mode/detector: combine multilingual patterns into single regex - parser: skip redundant toLowerCase on pre-lowered keywords - edit-operations: use fast arraysEqual instead of JSON comparison - hash-computation: optimize streaming line extraction with index tracking
104 lines
2.8 KiB
TypeScript
104 lines
2.8 KiB
TypeScript
import { dedupeEdits } from "./edit-deduplication"
|
|
import { collectLineRefs, detectOverlappingRanges, getEditLineNumber } from "./edit-ordering"
|
|
import type { HashlineEdit } from "./types"
|
|
import {
|
|
applyAppend,
|
|
applyInsertAfter,
|
|
applyInsertBefore,
|
|
applyPrepend,
|
|
applyReplaceLines,
|
|
applySetLine,
|
|
} from "./edit-operation-primitives"
|
|
import { validateLineRefs } from "./validation"
|
|
|
|
function arraysEqual(a: string[], b: string[]): boolean {
|
|
if (a.length !== b.length) return false
|
|
for (let i = 0; i < a.length; i++) {
|
|
if (a[i] !== b[i]) return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
export interface HashlineApplyReport {
|
|
content: string
|
|
noopEdits: number
|
|
deduplicatedEdits: number
|
|
}
|
|
|
|
export function applyHashlineEditsWithReport(content: string, edits: HashlineEdit[]): HashlineApplyReport {
|
|
if (edits.length === 0) {
|
|
return {
|
|
content,
|
|
noopEdits: 0,
|
|
deduplicatedEdits: 0,
|
|
}
|
|
}
|
|
|
|
const dedupeResult = dedupeEdits(edits)
|
|
const EDIT_PRECEDENCE: Record<string, number> = { replace: 0, append: 1, prepend: 2 }
|
|
const sortedEdits = [...dedupeResult.edits].sort((a, b) => {
|
|
const lineA = getEditLineNumber(a)
|
|
const lineB = getEditLineNumber(b)
|
|
if (lineB !== lineA) return lineB - lineA
|
|
return (EDIT_PRECEDENCE[a.op] ?? 3) - (EDIT_PRECEDENCE[b.op] ?? 3)
|
|
})
|
|
|
|
let noopEdits = 0
|
|
|
|
let lines = content.length === 0 ? [] : content.split("\n")
|
|
|
|
const refs = collectLineRefs(sortedEdits)
|
|
validateLineRefs(lines, refs)
|
|
|
|
const overlapError = detectOverlappingRanges(sortedEdits)
|
|
if (overlapError) throw new Error(overlapError)
|
|
|
|
for (const edit of sortedEdits) {
|
|
switch (edit.op) {
|
|
case "replace": {
|
|
const next = edit.end
|
|
? applyReplaceLines(lines, edit.pos, edit.end, edit.lines, { skipValidation: true })
|
|
: applySetLine(lines, edit.pos, edit.lines, { skipValidation: true })
|
|
if (arraysEqual(next, lines)) {
|
|
noopEdits += 1
|
|
break
|
|
}
|
|
lines = next
|
|
break
|
|
}
|
|
case "append": {
|
|
const next = edit.pos
|
|
? applyInsertAfter(lines, edit.pos, edit.lines, { skipValidation: true })
|
|
: applyAppend(lines, edit.lines)
|
|
if (arraysEqual(next, lines)) {
|
|
noopEdits += 1
|
|
break
|
|
}
|
|
lines = next
|
|
break
|
|
}
|
|
case "prepend": {
|
|
const next = edit.pos
|
|
? applyInsertBefore(lines, edit.pos, edit.lines, { skipValidation: true })
|
|
: applyPrepend(lines, edit.lines)
|
|
if (arraysEqual(next, lines)) {
|
|
noopEdits += 1
|
|
break
|
|
}
|
|
lines = next
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
content: lines.join("\n"),
|
|
noopEdits,
|
|
deduplicatedEdits: dedupeResult.deduplicatedEdits,
|
|
}
|
|
}
|
|
|
|
export function applyHashlineEdits(content: string, edits: HashlineEdit[]): string {
|
|
return applyHashlineEditsWithReport(content, edits).content
|
|
}
|