mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 11:25:50 -06:00
refactor(agents): use diff native APIs
This commit is contained in:
@@ -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[] = [];
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user