359c6b6655
- P2: Change replace edit sorting from POSITIVE_INFINITY to NEGATIVE_INFINITY so replace edits run LAST after line-based edits, preventing line number shifts that would invalidate subsequent anchors - P3: Update tool description from SHA-256 to xxHash32 to match actual implementation in hash-computation.ts
124 lines
3.7 KiB
TypeScript
124 lines
3.7 KiB
TypeScript
import { parseLineRef, validateLineRef } from "./validation"
|
|
import type { HashlineEdit } from "./types"
|
|
|
|
function unescapeNewlines(text: string): string {
|
|
return text.replace(/\\n/g, "\n")
|
|
}
|
|
|
|
export function applySetLine(lines: string[], anchor: string, newText: string): string[] {
|
|
validateLineRef(lines, anchor)
|
|
const { line } = parseLineRef(anchor)
|
|
const result = [...lines]
|
|
result[line - 1] = unescapeNewlines(newText)
|
|
return result
|
|
}
|
|
|
|
export function applyReplaceLines(
|
|
lines: string[],
|
|
startAnchor: string,
|
|
endAnchor: string,
|
|
newText: string
|
|
): string[] {
|
|
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 newLines = unescapeNewlines(newText).split("\n")
|
|
result.splice(startLine - 1, endLine - startLine + 1, ...newLines)
|
|
return result
|
|
}
|
|
|
|
export function applyInsertAfter(lines: string[], anchor: string, text: string): string[] {
|
|
validateLineRef(lines, anchor)
|
|
const { line } = parseLineRef(anchor)
|
|
const result = [...lines]
|
|
const newLines = unescapeNewlines(text).split("\n")
|
|
result.splice(line, 0, ...newLines)
|
|
return result
|
|
}
|
|
|
|
export function applyReplace(content: string, oldText: string, newText: string): string {
|
|
if (!content.includes(oldText)) {
|
|
throw new Error(`Text not found: "${oldText}"`)
|
|
}
|
|
return content.replaceAll(oldText, unescapeNewlines(newText))
|
|
}
|
|
|
|
function getEditLineNumber(edit: HashlineEdit): number {
|
|
switch (edit.type) {
|
|
case "set_line":
|
|
return parseLineRef(edit.line).line
|
|
case "replace_lines":
|
|
return parseLineRef(edit.end_line).line
|
|
case "insert_after":
|
|
return parseLineRef(edit.line).line
|
|
case "replace":
|
|
return Number.NEGATIVE_INFINITY
|
|
default:
|
|
return Number.POSITIVE_INFINITY
|
|
}
|
|
}
|
|
|
|
export function applyHashlineEdits(content: string, edits: HashlineEdit[]): string {
|
|
if (edits.length === 0) {
|
|
return content
|
|
}
|
|
|
|
const sortedEdits = [...edits].sort((a, b) => getEditLineNumber(b) - getEditLineNumber(a))
|
|
|
|
let result = content
|
|
let lines = result.split("\n")
|
|
|
|
for (const edit of sortedEdits) {
|
|
switch (edit.type) {
|
|
case "set_line": {
|
|
validateLineRef(lines, edit.line)
|
|
const { line } = parseLineRef(edit.line)
|
|
lines[line - 1] = unescapeNewlines(edit.text)
|
|
break
|
|
}
|
|
case "replace_lines": {
|
|
validateLineRef(lines, edit.start_line)
|
|
validateLineRef(lines, edit.end_line)
|
|
const { line: startLine } = parseLineRef(edit.start_line)
|
|
const { line: endLine } = parseLineRef(edit.end_line)
|
|
if (startLine > endLine) {
|
|
throw new Error(
|
|
`Invalid range: start line ${startLine} cannot be greater than end line ${endLine}`
|
|
)
|
|
}
|
|
const newLines = unescapeNewlines(edit.text).split("\n")
|
|
lines.splice(startLine - 1, endLine - startLine + 1, ...newLines)
|
|
break
|
|
}
|
|
case "insert_after": {
|
|
validateLineRef(lines, edit.line)
|
|
const { line } = parseLineRef(edit.line)
|
|
const newLines = unescapeNewlines(edit.text).split("\n")
|
|
lines.splice(line, 0, ...newLines)
|
|
break
|
|
}
|
|
case "replace": {
|
|
result = lines.join("\n")
|
|
if (!result.includes(edit.old_text)) {
|
|
throw new Error(`Text not found: "${edit.old_text}"`)
|
|
}
|
|
result = result.replaceAll(edit.old_text, unescapeNewlines(edit.new_text))
|
|
lines = result.split("\n")
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return lines.join("\n")
|
|
}
|