| |
| |
| |
| |
| |
| import { diffWords } from "diff"; |
| import { interactiveAgentTheme as theme } from "../theme/theme.js"; |
|
|
| |
| |
| |
| |
| function parseDiffLine(line: string): { prefix: string; lineNum: string; content: string } | null { |
| const match = line.match(/^([+-\s])(\s*\d*)\s(.*)$/); |
| if (!match) { |
| return null; |
| } |
| const [, prefix, lineNum, content] = match; |
| return prefix !== undefined && lineNum !== undefined && content !== undefined |
| ? { prefix, lineNum, content } |
| : null; |
| } |
|
|
| |
| |
| |
| function replaceTabs(text: string): string { |
| return text.replace(/\t/g, " "); |
| } |
|
|
| |
| |
| |
| |
| |
| function renderIntraLineDiff( |
| oldContent: string, |
| newContent: string, |
| ): { removedLine: string; addedLine: string } { |
| let removedLine = ""; |
| let addedLine = ""; |
| const seen = { added: false, removed: false }; |
|
|
| for (const part of diffWords(oldContent, newContent)) { |
| const kind = part.added ? "added" : part.removed ? "removed" : undefined; |
| let value = part.value; |
| if (kind) { |
| const changed = seen[kind] ? value : value.trimStart(); |
| const leadingWhitespace = value.slice(0, value.length - changed.length); |
| value = leadingWhitespace + (changed ? theme.inverse(changed) : ""); |
| seen[kind] = true; |
| } |
| if (!part.added) { |
| removedLine += value; |
| } |
| if (!part.removed) { |
| addedLine += value; |
| } |
| } |
|
|
| return { removedLine, addedLine }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function renderDiff(diffText: string): string { |
| const lines = diffText.split("\n"); |
| const result: string[] = []; |
|
|
| let i = 0; |
| while (i < lines.length) { |
| const line = lines.at(i); |
| if (line === undefined) { |
| break; |
| } |
| const parsed = parseDiffLine(line); |
|
|
| if (!parsed) { |
| result.push(theme.fg("toolDiffContext", line)); |
| i++; |
| continue; |
| } |
|
|
| if (parsed.prefix === "-") { |
| |
| const removedLines: { lineNum: string; content: string }[] = []; |
| while (i < lines.length) { |
| const currentLine = lines.at(i); |
| const p = currentLine === undefined ? null : parseDiffLine(currentLine); |
| if (!p || p.prefix !== "-") { |
| break; |
| } |
| removedLines.push({ lineNum: p.lineNum, content: p.content }); |
| i++; |
| } |
|
|
| |
| const addedLines: { lineNum: string; content: string }[] = []; |
| while (i < lines.length) { |
| const currentLine = lines.at(i); |
| const p = currentLine === undefined ? null : parseDiffLine(currentLine); |
| if (!p || p.prefix !== "+") { |
| break; |
| } |
| addedLines.push({ lineNum: p.lineNum, content: p.content }); |
| i++; |
| } |
|
|
| |
| |
| if (removedLines.length === 1 && addedLines.length === 1) { |
| const removed = removedLines[0]; |
| const added = addedLines[0]; |
| if (!removed || !added) { |
| continue; |
| } |
|
|
| const { removedLine, addedLine } = renderIntraLineDiff( |
| replaceTabs(removed.content), |
| replaceTabs(added.content), |
| ); |
|
|
| result.push(theme.fg("toolDiffRemoved", `-${removed.lineNum} ${removedLine}`)); |
| result.push(theme.fg("toolDiffAdded", `+${added.lineNum} ${addedLine}`)); |
| } else { |
| |
| for (const removed of removedLines) { |
| result.push( |
| theme.fg("toolDiffRemoved", `-${removed.lineNum} ${replaceTabs(removed.content)}`), |
| ); |
| } |
| for (const added of addedLines) { |
| result.push(theme.fg("toolDiffAdded", `+${added.lineNum} ${replaceTabs(added.content)}`)); |
| } |
| } |
| } else if (parsed.prefix === "+") { |
| |
| result.push(theme.fg("toolDiffAdded", `+${parsed.lineNum} ${replaceTabs(parsed.content)}`)); |
| i++; |
| } else { |
| |
| result.push(theme.fg("toolDiffContext", ` ${parsed.lineNum} ${replaceTabs(parsed.content)}`)); |
| i++; |
| } |
| } |
|
|
| return result.join("\n"); |
| } |
|
|