From e5e8d8717eb95a5000cb7d71c30cde4663e8ce6f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 14 Jul 2026 12:29:35 +0100 Subject: [PATCH] refactor(agents): use diff native APIs --- .../modes/interactive/components/diff.ts | 56 ++++++------------- src/agents/sessions/tools/edit-diff.ts | 8 +-- src/agents/sessions/tools/edit.ts | 12 +--- 3 files changed, 23 insertions(+), 53 deletions(-) diff --git a/src/agents/modes/interactive/components/diff.ts b/src/agents/modes/interactive/components/diff.ts index b3383fafdb35..0e37809d4c80 100644 --- a/src/agents/modes/interactive/components/diff.ts +++ b/src/agents/modes/interactive/components/diff.ts @@ -3,7 +3,7 @@ * * Produces colored line and intra-line highlights for the Pi TUI review surfaces. */ -import * as Diff from "diff"; +import { diffWords } from "diff"; import { theme } from "../theme/theme.js"; /** @@ -37,59 +37,37 @@ function renderIntraLineDiff( oldContent: string, newContent: string, ): { removedLine: string; addedLine: string } { - const wordDiff = Diff.diffWords(oldContent, newContent); - let removedLine = ""; let addedLine = ""; - let isFirstRemoved = true; - let isFirstAdded = true; + const seen = { added: false, removed: false }; - for (const part of wordDiff) { - if (part.removed) { - let value = part.value; - // Strip leading whitespace from the first removed part - if (isFirstRemoved) { - const leadingWs = value.match(/^(\s*)/)?.[1] || ""; - value = value.slice(leadingWs.length); - removedLine += leadingWs; - isFirstRemoved = false; - } - if (value) { - removedLine += theme.inverse(value); - } - } else if (part.added) { - let value = part.value; - // Strip leading whitespace from the first added part - if (isFirstAdded) { - const leadingWs = value.match(/^(\s*)/)?.[1] || ""; - value = value.slice(leadingWs.length); - addedLine += leadingWs; - isFirstAdded = false; - } - if (value) { - addedLine += theme.inverse(value); - } - } else { - removedLine += part.value; - addedLine += part.value; + 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 }; } -interface RenderDiffOptions { - /** File path (unused, kept for API compatibility) */ - filePath?: string; -} - /** * Render a diff string with colored lines and intra-line change highlighting. * - Context lines: dim/gray * - Removed lines: red, with inverse on changed tokens * - Added lines: green, with inverse on changed tokens */ -export function renderDiff(diffText: string, _options: RenderDiffOptions = {}): string { +export function renderDiff(diffText: string): string { const lines = diffText.split("\n"); const result: string[] = []; diff --git a/src/agents/sessions/tools/edit-diff.ts b/src/agents/sessions/tools/edit-diff.ts index 39484b1fc03f..898d529ee4ce 100644 --- a/src/agents/sessions/tools/edit-diff.ts +++ b/src/agents/sessions/tools/edit-diff.ts @@ -5,7 +5,7 @@ import { constants } from "node:fs"; import { access, readFile } from "node:fs/promises"; -import * as Diff from "diff"; +import { createPatch, FILE_HEADERS_ONLY, structuredPatch } from "diff"; import { levenshteinDistance } from "../../../shared/levenshtein-distance.js"; import { resolveToCwd } from "./path-utils.js"; @@ -540,9 +540,9 @@ export function generateUnifiedPatch( newContent: string, contextLines = 4, ): string { - return Diff.createTwoFilesPatch(path, path, oldContent, newContent, undefined, undefined, { + return createPatch(path, oldContent, newContent, undefined, undefined, { context: contextLines, - headerOptions: Diff.FILE_HEADERS_ONLY, + headerOptions: FILE_HEADERS_ONLY, }); } @@ -555,7 +555,7 @@ export function generateDiffString( newContent: string, contextLines = 4, ): { diff: string; firstChangedLine: number | undefined } { - const hunks = Diff.structuredPatch("", "", oldContent, newContent, undefined, undefined, { + const hunks = structuredPatch("", "", oldContent, newContent, undefined, undefined, { context: contextLines, }).hunks; const oldLineCount = oldContent.split("\n").length; diff --git a/src/agents/sessions/tools/edit.ts b/src/agents/sessions/tools/edit.ts index b0b7ae64049a..be7e869534dd 100644 --- a/src/agents/sessions/tools/edit.ts +++ b/src/agents/sessions/tools/edit.ts @@ -291,13 +291,11 @@ function formatEditCall( } function formatEditResult( - args: RenderableEditArgs | undefined, preview: EditPreview | undefined, result: EditToolResultLike, theme: typeof import("../../modes/interactive/theme/theme.js").theme, isError: boolean, ): string | undefined { - const rawPath = str(args?.file_path ?? args?.path); const previewDiff = preview && !("error" in preview) ? preview.diff : undefined; const previewError = preview && "error" in preview ? preview.error : undefined; if (isError) { @@ -313,7 +311,7 @@ function formatEditResult( const resultDiff = result.details?.diff; if (resultDiff && resultDiff !== previewDiff) { - return renderDiff(resultDiff, { filePath: rawPath ?? undefined }); + return renderDiff(resultDiff); } return undefined; @@ -579,13 +577,7 @@ export function createEditToolDefinition( } } - const output = formatEditResult( - context.args, - callComponent?.preview, - typedResult, - theme, - context.isError, - ); + const output = formatEditResult(callComponent?.preview, typedResult, theme, context.isError); const component = (context.lastComponent as Container | undefined) ?? new Container(); component.clear(); if (!output) {