- Rewrite restoreOldWrappedLines to use oh-my-pi's span-scanning algorithm - Add stripTrailingContinuationTokens and stripMergeOperatorChars helpers - Fix detectLineEnding to use first-occurrence logic instead of any-match - Fix applyAppend/applyPrepend to replace empty-line placeholder in empty files - Enhance tool description with 7 critical rules, tag guidance, and anti-patterns
45 lines
1.3 KiB
TypeScript
45 lines
1.3 KiB
TypeScript
export interface FileTextEnvelope {
|
|
content: string
|
|
hadBom: boolean
|
|
lineEnding: "\n" | "\r\n"
|
|
}
|
|
|
|
function detectLineEnding(content: string): "\n" | "\r\n" {
|
|
const crlfIndex = content.indexOf("\r\n")
|
|
const lfIndex = content.indexOf("\n")
|
|
if (lfIndex === -1) return "\n"
|
|
if (crlfIndex === -1) return "\n"
|
|
return crlfIndex < lfIndex ? "\r\n" : "\n"
|
|
}
|
|
|
|
function stripBom(content: string): { content: string; hadBom: boolean } {
|
|
if (!content.startsWith("\uFEFF")) {
|
|
return { content, hadBom: false }
|
|
}
|
|
return { content: content.slice(1), hadBom: true }
|
|
}
|
|
|
|
function normalizeToLf(content: string): string {
|
|
return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n")
|
|
}
|
|
|
|
function restoreLineEndings(content: string, lineEnding: "\n" | "\r\n"): string {
|
|
if (lineEnding === "\n") return content
|
|
return content.replace(/\n/g, "\r\n")
|
|
}
|
|
|
|
export function canonicalizeFileText(content: string): FileTextEnvelope {
|
|
const stripped = stripBom(content)
|
|
return {
|
|
content: normalizeToLf(stripped.content),
|
|
hadBom: stripped.hadBom,
|
|
lineEnding: detectLineEnding(stripped.content),
|
|
}
|
|
}
|
|
|
|
export function restoreFileText(content: string, envelope: FileTextEnvelope): string {
|
|
const withLineEnding = restoreLineEndings(content, envelope.lineEnding)
|
|
if (!envelope.hadBom) return withLineEnding
|
|
return `\uFEFF${withLineEnding}`
|
|
}
|