@heyhuynhgiabuu/pi-diff 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/hashline-execute.d.ts +3 -3
- package/dist/core/hashline-execute.d.ts.map +1 -1
- package/dist/core/hashline-execute.js +2 -2
- package/dist/core/hashline-execute.js.map +1 -1
- package/dist/hashline.d.ts +1 -1
- package/dist/hashline.d.ts.map +1 -1
- package/dist/hashline.js +11 -4
- package/dist/hashline.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +248 -293
- package/dist/index.js.map +1 -1
- package/dist/review/hunk-preview.d.ts +5 -2
- package/dist/review/hunk-preview.d.ts.map +1 -1
- package/dist/review/hunk-preview.js +16 -11
- package/dist/review/hunk-preview.js.map +1 -1
- package/package.json +115 -115
package/dist/index.js
CHANGED
|
@@ -21,16 +21,14 @@
|
|
|
21
21
|
* • Async rendering with invalidate() for non-blocking preview
|
|
22
22
|
*/
|
|
23
23
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
24
|
-
import { extname, relative
|
|
24
|
+
import { extname, relative } from "node:path";
|
|
25
25
|
import { codeToANSI } from "@shikijs/cli";
|
|
26
26
|
import * as Diff from "diff";
|
|
27
|
+
import { executeApplyPatch, formatApplyPatchResult } from "./core/apply-patch.js";
|
|
27
28
|
import { configIndicatorStyle } from "./core/config.js";
|
|
28
29
|
import { computeHunkBlocks, getSepStyle, parseDiff, parsePatchFiles, resolveSepStyle, sepLabelSplit, sepLabelUnified, } from "./core/diff.js";
|
|
29
30
|
import { replace } from "./core/replace.js";
|
|
30
31
|
import { registerEditGuard } from "./edit-guard.js";
|
|
31
|
-
import { initHashline } from "./hashline.js";
|
|
32
|
-
import { executeHashlineRead, HASHLINE_EDIT_DESC, HASHLINE_READ_DESC, runHashlineEdit, } from "./core/hashline-execute.js";
|
|
33
|
-
import * as fs from "node:fs";
|
|
34
32
|
import { applyDiffPalette as applySharedDiffPalette, lang as detectDiffLanguage, renderSplit as renderSharedSplit, resolveDiffColors as resolveSharedDiffColors, themeCacheKey as sharedThemeCacheKey, } from "./review/hunk-preview.js";
|
|
35
33
|
const DIFF_PRESETS = {
|
|
36
34
|
default: {
|
|
@@ -207,10 +205,7 @@ function autoDeriveBgFromTheme(theme) {
|
|
|
207
205
|
}
|
|
208
206
|
/** Load diff theme config from .pi/settings.json (project-level, then global). */
|
|
209
207
|
function loadDiffConfig() {
|
|
210
|
-
const paths = [
|
|
211
|
-
`${process.cwd()}/.pi/settings.json`,
|
|
212
|
-
`${process.env.HOME ?? ""}/.pi/settings.json`,
|
|
213
|
-
];
|
|
208
|
+
const paths = [`${process.cwd()}/.pi/settings.json`, `${process.env.HOME ?? ""}/.pi/settings.json`];
|
|
214
209
|
for (const p of paths) {
|
|
215
210
|
try {
|
|
216
211
|
if (existsSync(p)) {
|
|
@@ -923,10 +918,7 @@ async function renderUnified(diff, language, max = MAX_RENDER_LINES, dc = DEFAUL
|
|
|
923
918
|
newSrc.push(l.content);
|
|
924
919
|
}
|
|
925
920
|
const [oldHL, newHL] = canHL
|
|
926
|
-
? await Promise.all([
|
|
927
|
-
hlBlock(oldSrc.join("\n"), language),
|
|
928
|
-
hlBlock(newSrc.join("\n"), language),
|
|
929
|
-
])
|
|
921
|
+
? await Promise.all([hlBlock(oldSrc.join("\n"), language), hlBlock(newSrc.join("\n"), language)])
|
|
930
922
|
: [oldSrc, newSrc];
|
|
931
923
|
let oI = 0, nI = 0, idx = 0;
|
|
932
924
|
const out = [];
|
|
@@ -1069,10 +1061,7 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1069
1061
|
rightSrc.push(r.right.content);
|
|
1070
1062
|
}
|
|
1071
1063
|
const [leftHL, rightHL] = canHL
|
|
1072
|
-
? await Promise.all([
|
|
1073
|
-
hlBlock(leftSrc.join("\n"), language),
|
|
1074
|
-
hlBlock(rightSrc.join("\n"), language),
|
|
1075
|
-
])
|
|
1064
|
+
? await Promise.all([hlBlock(leftSrc.join("\n"), language), hlBlock(rightSrc.join("\n"), language)])
|
|
1076
1065
|
: [leftSrc, rightSrc];
|
|
1077
1066
|
let lI = 0, rI = 0;
|
|
1078
1067
|
let stripeRow = 0; // tracks row index for diagonal stripe offset
|
|
@@ -1094,13 +1083,7 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1094
1083
|
const cBg = isDel ? BG_DEL : isAdd ? BG_ADD : BG_BASE;
|
|
1095
1084
|
const sFg = isDel ? dc.fgDel : isAdd ? dc.fgAdd : dc.fgCtx;
|
|
1096
1085
|
const sign = isDel ? "-" : isAdd ? "+" : " ";
|
|
1097
|
-
const num = isDel
|
|
1098
|
-
? line.oldNum
|
|
1099
|
-
: isAdd
|
|
1100
|
-
? line.newNum
|
|
1101
|
-
: side === "left"
|
|
1102
|
-
? line.oldNum
|
|
1103
|
-
: line.newNum;
|
|
1086
|
+
const num = isDel ? line.oldNum : isAdd ? line.newNum : side === "left" ? line.oldNum : line.newNum;
|
|
1104
1087
|
// Border bar + colored line numbers for changed lines
|
|
1105
1088
|
const borderFg = isDel ? dc.fgDel : isAdd ? dc.fgAdd : "";
|
|
1106
1089
|
const border = borderFg ? `${borderFg}${getBorderBar()}${RST}` : `${BG_BASE}`;
|
|
@@ -1217,31 +1200,20 @@ export default async function diffRendererExtension(pi) {
|
|
|
1217
1200
|
return;
|
|
1218
1201
|
writeHeaderStatsByCallId.set(toolCallId, { added, removed });
|
|
1219
1202
|
}
|
|
1220
|
-
const hashlineEditHeaderStatsByCallId = new Map();
|
|
1221
|
-
function stashHashlineEditHeaderStats(toolCallId, diffLines, added, removed) {
|
|
1222
|
-
if (!toolCallId)
|
|
1223
|
-
return;
|
|
1224
|
-
hashlineEditHeaderStatsByCallId.set(toolCallId, { diffLines, added, removed });
|
|
1225
|
-
}
|
|
1226
|
-
function hashlineEditCallStatsSuffix(toolCallId, theme) {
|
|
1227
|
-
const raw = toolCallId ? hashlineEditHeaderStatsByCallId.get(toolCallId) : undefined;
|
|
1228
|
-
if (!raw)
|
|
1229
|
-
return "";
|
|
1230
|
-
const loc = raw.diffLines > 0
|
|
1231
|
-
? `${theme.fg("muted", `${raw.diffLines} diff line${raw.diffLines === 1 ? "" : "s"}`)} `
|
|
1232
|
-
: "";
|
|
1233
|
-
return `${TOOL_RESULT_INDENT}${loc}${summarizeThemed(raw.added, raw.removed, theme)}`;
|
|
1234
|
-
}
|
|
1235
1203
|
const cwd = process.cwd();
|
|
1236
1204
|
const home = process.env.HOME ?? "";
|
|
1237
1205
|
const sp = (p) => shortPath(cwd, home, p);
|
|
1238
1206
|
const TOOL_RESULT_INDENT = " ";
|
|
1239
1207
|
const TOOL_HEADER_LEFT_PAD = 0;
|
|
1240
1208
|
const DIFF_BODY_LEFT_PAD = 0;
|
|
1241
|
-
/**
|
|
1242
|
-
const EDIT_DIFF_RESULT_FRAME = {
|
|
1243
|
-
|
|
1244
|
-
|
|
1209
|
+
/** Built-in `edit` tool diff result frame; intentionally offset from read/write/apply_patch previews. */
|
|
1210
|
+
const EDIT_DIFF_RESULT_FRAME = {
|
|
1211
|
+
headerLeftPad: 1,
|
|
1212
|
+
bodyLeftPad: 1,
|
|
1213
|
+
topPad: 1,
|
|
1214
|
+
bottomPad: 0,
|
|
1215
|
+
previewBottomPad: 1,
|
|
1216
|
+
};
|
|
1245
1217
|
function resolvePreviewDiffColors(theme) {
|
|
1246
1218
|
resolveDiffColors(theme);
|
|
1247
1219
|
return resolveSharedDiffColors(theme);
|
|
@@ -1251,19 +1223,114 @@ export default async function diffRendererExtension(pi) {
|
|
|
1251
1223
|
const padding = " ".repeat(Math.max(0, renderWidth - strip(content).length));
|
|
1252
1224
|
return injectBg(`${content}${padding}`, [], BG_BASE, BG_BASE);
|
|
1253
1225
|
}
|
|
1254
|
-
function
|
|
1255
|
-
const {
|
|
1226
|
+
function formatToolFrameHeaderText(opts) {
|
|
1227
|
+
const { topPad = 0, bottomPad = 0, headerLeftPad, suffix = "", label, filePath, theme, meta } = opts;
|
|
1256
1228
|
const leftPad = " ".repeat(headerLeftPad ?? TOOL_HEADER_LEFT_PAD);
|
|
1257
|
-
const content =
|
|
1258
|
-
? `${leftPad}${
|
|
1259
|
-
: `${leftPad}${
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1229
|
+
const content = meta !== undefined && meta !== null
|
|
1230
|
+
? `${leftPad}${meta}${suffix}`
|
|
1231
|
+
: `${leftPad}${theme.fg("toolTitle", theme.bold(label ?? ""))} ${theme.fg("accent", sp(filePath ?? ""))}${suffix}`;
|
|
1232
|
+
return `${"\n".repeat(topPad)}${content}${"\n".repeat(bottomPad)}`;
|
|
1233
|
+
}
|
|
1234
|
+
function formatToolFrameHeader(opts) {
|
|
1235
|
+
const { width, ...rest } = opts;
|
|
1236
|
+
return bgLine(formatToolFrameHeaderText(rest), width);
|
|
1237
|
+
}
|
|
1238
|
+
function setToolHeaderBg(text) {
|
|
1239
|
+
text.customBgFn = (line) => injectBg(line, [], BG_BASE, BG_BASE);
|
|
1240
|
+
}
|
|
1241
|
+
function clearToolHeaderBg(text) {
|
|
1242
|
+
text.customBgFn = undefined;
|
|
1243
|
+
}
|
|
1244
|
+
function summarizeApplyPatchChanges(changes, theme) {
|
|
1245
|
+
const labels = changes.map((change) => theme.fg("accent", sp(change.path)));
|
|
1246
|
+
if (labels.length <= 1)
|
|
1247
|
+
return labels.join("");
|
|
1248
|
+
return `${labels[0]}${theme.fg("muted", `, +${labels.length - 1} more`)}`;
|
|
1249
|
+
}
|
|
1250
|
+
function renderApplyPatchPreview(text, result, theme, ctx) {
|
|
1251
|
+
const applied = Array.isArray(result?.applied) ? result.applied : [];
|
|
1252
|
+
if (!applied.length)
|
|
1253
|
+
return false;
|
|
1254
|
+
const w = termW();
|
|
1255
|
+
const previewable = applied.filter((change) => {
|
|
1256
|
+
if (typeof change?.path !== "string")
|
|
1257
|
+
return false;
|
|
1258
|
+
if (change.action === "add")
|
|
1259
|
+
return typeof change.newContent === "string";
|
|
1260
|
+
if (change.action === "update" || change.action === "delete")
|
|
1261
|
+
return typeof change.oldContent === "string";
|
|
1262
|
+
return false;
|
|
1263
|
+
});
|
|
1264
|
+
if (previewable.length !== applied.length)
|
|
1265
|
+
return false;
|
|
1266
|
+
if (previewable.length === 1) {
|
|
1267
|
+
const change = previewable[0];
|
|
1268
|
+
if (change.action === "add" && typeof change.newContent === "string") {
|
|
1269
|
+
clearToolHeaderBg(text);
|
|
1270
|
+
resolvePreviewDiffColors(theme);
|
|
1271
|
+
const lineCount = change.newContent.split("\n").length;
|
|
1272
|
+
const newHdr = bgLine(`${theme.fg("success", `✓ new file (${lineCount} lines)`)}`, w);
|
|
1273
|
+
const fp = change.path;
|
|
1274
|
+
const pk = `ap:nf:${sharedThemeCacheKey(theme)}:${fp}:${lineCount}`;
|
|
1275
|
+
if (ctx.state._nfk !== pk) {
|
|
1276
|
+
ctx.state._nfk = pk;
|
|
1277
|
+
const lg = detectDiffLanguage(fp);
|
|
1278
|
+
text.__piDiffTask = {
|
|
1279
|
+
placeholder: `${newHdr}\n${padDiffBody(theme.fg("muted", "rendering file…"))}`,
|
|
1280
|
+
fallback: `${newHdr}`,
|
|
1281
|
+
invalidate: ctx.invalidate,
|
|
1282
|
+
key: (width) => `ap:nf:${sharedThemeCacheKey(theme)}:${fp}:${lineCount}:${width}`,
|
|
1283
|
+
render: async (_width) => {
|
|
1284
|
+
const hlLines = await hlBlock(change.newContent, lg);
|
|
1285
|
+
const preview = hlLines.join("\n").replace(/\n+$/, "");
|
|
1286
|
+
return `${newHdr}\n${padDiffBody(preview)}`;
|
|
1287
|
+
},
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
return true;
|
|
1291
|
+
}
|
|
1292
|
+
if ((change.action === "update" || change.action === "delete") &&
|
|
1293
|
+
typeof change.oldContent === "string" &&
|
|
1294
|
+
typeof change.path === "string") {
|
|
1295
|
+
const parsed = parseDiff(change.oldContent, change.newContent ?? "");
|
|
1296
|
+
setDiffPreviewTask(text, "ap", (width) => formatToolFrameHeader({
|
|
1297
|
+
meta: `${theme.fg("toolTitle", theme.bold("apply_patch"))}${TOOL_RESULT_INDENT}${theme.fg("muted", `(1 change)`)}${TOOL_RESULT_INDENT}${theme.fg("accent", sp(change.path))}`,
|
|
1298
|
+
theme,
|
|
1299
|
+
width,
|
|
1300
|
+
topPad: 0,
|
|
1301
|
+
bottomPad: 0,
|
|
1302
|
+
}), parsed, detectDiffLanguage(change.path), MAX_PREVIEW_LINES, theme, ctx, { previewBottomPad: 1, compactGutter: true });
|
|
1303
|
+
return true;
|
|
1304
|
+
}
|
|
1305
|
+
return false;
|
|
1306
|
+
}
|
|
1307
|
+
clearToolHeaderBg(text);
|
|
1308
|
+
let added = 0;
|
|
1309
|
+
let removed = 0;
|
|
1310
|
+
let chars = 0;
|
|
1311
|
+
let language;
|
|
1312
|
+
let mixedLanguage = false;
|
|
1313
|
+
const lines = [];
|
|
1314
|
+
for (const change of previewable) {
|
|
1315
|
+
const parsed = parseDiff(change.oldContent ?? "", change.newContent ?? "");
|
|
1316
|
+
const nextLanguage = detectDiffLanguage(change.path);
|
|
1317
|
+
if (!language)
|
|
1318
|
+
language = nextLanguage;
|
|
1319
|
+
else if (language !== nextLanguage)
|
|
1320
|
+
mixedLanguage = true;
|
|
1321
|
+
added += parsed.added;
|
|
1322
|
+
removed += parsed.removed;
|
|
1323
|
+
chars += parsed.chars;
|
|
1324
|
+
lines.push(...parsed.lines);
|
|
1325
|
+
}
|
|
1326
|
+
setDiffPreviewTask(text, "ap", (width) => formatToolFrameHeader({
|
|
1327
|
+
meta: `${theme.fg("toolTitle", theme.bold("apply_patch"))}${TOOL_RESULT_INDENT}${theme.fg("muted", `(${previewable.length} changes)`)} ${summarizeThemed(added, removed, theme)}${TOOL_RESULT_INDENT}${theme.fg("muted", summarizeApplyPatchChanges(previewable, theme))}`,
|
|
1328
|
+
theme,
|
|
1329
|
+
width,
|
|
1330
|
+
topPad: 0,
|
|
1331
|
+
bottomPad: 0,
|
|
1332
|
+
}), { lines, added, removed, chars }, mixedLanguage ? undefined : language, MAX_PREVIEW_LINES, theme, ctx, { previewBottomPad: 1, compactGutter: true });
|
|
1333
|
+
return true;
|
|
1267
1334
|
}
|
|
1268
1335
|
function editEditsCountLabel(edits, diffLines, theme) {
|
|
1269
1336
|
const n = edits === 1 ? "1 edit" : `${edits} edits`;
|
|
@@ -1279,9 +1346,7 @@ export default async function diffRendererExtension(pi) {
|
|
|
1279
1346
|
function formatEditDiffResultTitle(d, theme, width, frame) {
|
|
1280
1347
|
const fp = d.filePath ?? d.summary ?? "";
|
|
1281
1348
|
const edits = d.edits ?? d.editCount ?? 1;
|
|
1282
|
-
const diffLines = typeof d.diffLineCount === "number"
|
|
1283
|
-
? d.diffLineCount
|
|
1284
|
-
: (d.linesAdded ?? 0) + (d.linesRemoved ?? 0);
|
|
1349
|
+
const diffLines = typeof d.diffLineCount === "number" ? d.diffLineCount : (d.linesAdded ?? 0) + (d.linesRemoved ?? 0);
|
|
1285
1350
|
const suffix = `${TOOL_RESULT_INDENT}${theme.fg("muted", editEditsCountLabel(edits, diffLines, theme))} ${summarizeThemed(d.linesAdded ?? 0, d.linesRemoved ?? 0, theme)}`;
|
|
1286
1351
|
return formatToolFrameHeader({
|
|
1287
1352
|
width,
|
|
@@ -1300,16 +1365,20 @@ export default async function diffRendererExtension(pi) {
|
|
|
1300
1365
|
return "";
|
|
1301
1366
|
return `${TOOL_RESULT_INDENT}${summarizeThemed(raw.added, raw.removed, theme)}`;
|
|
1302
1367
|
}
|
|
1303
|
-
function padDiffBody(rendered) {
|
|
1304
|
-
const leftPad = `${BG_BASE}${" ".repeat(
|
|
1368
|
+
function padDiffBody(rendered, bodyLeftPad = DIFF_BODY_LEFT_PAD) {
|
|
1369
|
+
const leftPad = `${BG_BASE}${" ".repeat(bodyLeftPad)}${RST}`;
|
|
1305
1370
|
return rendered
|
|
1306
1371
|
.split("\n")
|
|
1307
1372
|
.map((line) => `${leftPad}${line}`)
|
|
1308
1373
|
.join("\n");
|
|
1309
1374
|
}
|
|
1310
|
-
async function renderPaddedDiff(diff, language, maxLines, colors, width) {
|
|
1311
|
-
const bodyWidth = Math.max(1, width -
|
|
1312
|
-
return padDiffBody(await renderSharedSplit(diff, language, maxLines, colors, bodyWidth));
|
|
1375
|
+
async function renderPaddedDiff(diff, language, maxLines, colors, width, bodyLeftPad = DIFF_BODY_LEFT_PAD) {
|
|
1376
|
+
const bodyWidth = Math.max(1, width - bodyLeftPad);
|
|
1377
|
+
return padDiffBody(await renderSharedSplit(diff, language, maxLines, colors, bodyWidth), bodyLeftPad);
|
|
1378
|
+
}
|
|
1379
|
+
async function renderPaddedCompactDiff(diff, language, maxLines, colors, width, bodyLeftPad = DIFF_BODY_LEFT_PAD) {
|
|
1380
|
+
const bodyWidth = Math.max(1, width - bodyLeftPad);
|
|
1381
|
+
return padDiffBody(await renderSharedSplit(diff, language, maxLines, colors, bodyWidth, { compactGutter: true }), bodyLeftPad);
|
|
1313
1382
|
}
|
|
1314
1383
|
function diffLineCountLabel(diffLineCount, theme) {
|
|
1315
1384
|
if (typeof diffLineCount !== "number")
|
|
@@ -1319,13 +1388,13 @@ export default async function diffRendererExtension(pi) {
|
|
|
1319
1388
|
function setToolHeaderText(text, meta, theme) {
|
|
1320
1389
|
resolvePreviewDiffColors(theme);
|
|
1321
1390
|
text.__piDiffTask = undefined;
|
|
1322
|
-
|
|
1323
|
-
text.setText(
|
|
1391
|
+
setToolHeaderBg(text);
|
|
1392
|
+
text.setText(formatToolFrameHeaderText({ meta, bottomPad: 0 }));
|
|
1324
1393
|
}
|
|
1325
1394
|
function setDiffPreviewTask(text, keyPrefix, metaOrHeader, diff, language, maxLines, theme, ctx, frame) {
|
|
1395
|
+
clearToolHeaderBg(text);
|
|
1326
1396
|
const themeKey = sharedThemeCacheKey(theme);
|
|
1327
1397
|
const colors = resolvePreviewDiffColors(theme);
|
|
1328
|
-
const headerKey = typeof metaOrHeader === "function" ? "fn:" + keyPrefix : metaOrHeader;
|
|
1329
1398
|
const header = frame?.omitHeader
|
|
1330
1399
|
? (_width) => ""
|
|
1331
1400
|
: typeof metaOrHeader === "function"
|
|
@@ -1345,11 +1414,16 @@ export default async function diffRendererExtension(pi) {
|
|
|
1345
1414
|
return bottom ? `${main}\n${bottom}` : main;
|
|
1346
1415
|
};
|
|
1347
1416
|
text.__piDiffTask = {
|
|
1348
|
-
placeholder: joinHeaderBody(termW(), padDiffBody(theme.fg("muted", " rendering diff…"))),
|
|
1417
|
+
placeholder: joinHeaderBody(termW(), padDiffBody(theme.fg("muted", " rendering diff…"), frame?.bodyLeftPad)),
|
|
1349
1418
|
fallback: header(termW()),
|
|
1350
1419
|
invalidate: ctx.invalidate,
|
|
1351
|
-
key: (width) =>
|
|
1352
|
-
|
|
1420
|
+
key: (width) => {
|
|
1421
|
+
const headerKey = frame?.omitHeader ? "" : header(width);
|
|
1422
|
+
return `${keyPrefix}:${themeKey}:${width}:${headerKey}:${diff.lines.length}:${language ?? ""}:${frame?.omitHeader ? "oh" : "h"}:${frame?.headerLeftPad ?? 0}:${frame?.topPad ?? 0}:${frame?.bottomPad ?? 0}:${frame?.previewBottomPad ?? 0}:${frame?.compactGutter ? "cg" : "rg"}:${frame?.bodyLeftPad ?? 0}`;
|
|
1423
|
+
},
|
|
1424
|
+
render: async (width) => joinHeaderBody(width, await (frame?.compactGutter
|
|
1425
|
+
? renderPaddedCompactDiff(diff, language, maxLines, colors, width, frame?.bodyLeftPad)
|
|
1426
|
+
: renderPaddedDiff(diff, language, maxLines, colors, width, frame?.bodyLeftPad))),
|
|
1353
1427
|
};
|
|
1354
1428
|
}
|
|
1355
1429
|
/** Wrap a Text component so its render(width) kicks off async diff rendering
|
|
@@ -1450,7 +1524,8 @@ export default async function diffRendererExtension(pi) {
|
|
|
1450
1524
|
if (args?.content && !ctx.argsComplete) {
|
|
1451
1525
|
const n = String(args.content).split("\n").length;
|
|
1452
1526
|
const suffix = `${TOOL_RESULT_INDENT}${theme.fg("muted", `(${n} lines…)`)}${stats ? ` ${stats.trimStart()}` : ""}`;
|
|
1453
|
-
text
|
|
1527
|
+
setToolHeaderBg(text);
|
|
1528
|
+
text.setText(formatToolFrameHeaderText({ label, filePath: fp, theme, suffix, topPad: 0, bottomPad: 0 }));
|
|
1454
1529
|
return text;
|
|
1455
1530
|
}
|
|
1456
1531
|
if (args?.content && ctx.argsComplete && isNew) {
|
|
@@ -1469,10 +1544,12 @@ export default async function diffRendererExtension(pi) {
|
|
|
1469
1544
|
})
|
|
1470
1545
|
.catch(() => { });
|
|
1471
1546
|
}
|
|
1547
|
+
clearToolHeaderBg(text);
|
|
1472
1548
|
text.setText(ctx.state._previewText ?? title);
|
|
1473
1549
|
return text;
|
|
1474
1550
|
}
|
|
1475
|
-
text
|
|
1551
|
+
setToolHeaderBg(text);
|
|
1552
|
+
text.setText(formatToolFrameHeaderText({ label, filePath: fp, theme, suffix: stats, topPad: 0, bottomPad: 0 }));
|
|
1476
1553
|
return text;
|
|
1477
1554
|
},
|
|
1478
1555
|
renderResult(result, _opt, theme, ctx) {
|
|
@@ -1483,21 +1560,28 @@ export default async function diffRendererExtension(pi) {
|
|
|
1483
1560
|
.map((c) => c.text || "")
|
|
1484
1561
|
.join("\n") ?? "Error";
|
|
1485
1562
|
text.__piDiffTask = undefined;
|
|
1563
|
+
clearToolHeaderBg(text);
|
|
1486
1564
|
text.setText(`\n${theme.fg("error", e)}`);
|
|
1487
1565
|
return text;
|
|
1488
1566
|
}
|
|
1489
1567
|
const d = result.details;
|
|
1490
1568
|
if (d?._type === "diff") {
|
|
1491
|
-
setDiffPreviewTask(text, "wd", "", d.diff, d.language, MAX_RENDER_LINES, theme, ctx, {
|
|
1569
|
+
setDiffPreviewTask(text, "wd", "", d.diff, d.language, MAX_RENDER_LINES, theme, ctx, {
|
|
1570
|
+
omitHeader: true,
|
|
1571
|
+
previewBottomPad: 1,
|
|
1572
|
+
compactGutter: true,
|
|
1573
|
+
});
|
|
1492
1574
|
return text;
|
|
1493
1575
|
}
|
|
1494
1576
|
if (d?._type === "noChange") {
|
|
1495
1577
|
text.__piDiffTask = undefined;
|
|
1578
|
+
clearToolHeaderBg(text);
|
|
1496
1579
|
text.setText(`${TOOL_RESULT_INDENT}${theme.fg("muted", "✓ no changes")}`);
|
|
1497
1580
|
return text;
|
|
1498
1581
|
}
|
|
1499
1582
|
if (d?._type === "new") {
|
|
1500
1583
|
const { lines: lineCount, content: rawContent, filePath: fp } = d;
|
|
1584
|
+
clearToolHeaderBg(text);
|
|
1501
1585
|
resolvePreviewDiffColors(theme);
|
|
1502
1586
|
const w = termW();
|
|
1503
1587
|
const newHdr = bgLine(`${theme.fg("success", `✓ new file (${lineCount} lines)`)}`, w);
|
|
@@ -1517,15 +1601,14 @@ export default async function diffRendererExtension(pi) {
|
|
|
1517
1601
|
const maxShow = hlLines.length;
|
|
1518
1602
|
const preview = hlLines.slice(0, maxShow).join("\n").replace(/\n+$/, "");
|
|
1519
1603
|
const rem = hlLines.length - maxShow;
|
|
1520
|
-
const moreLine = rem > 0
|
|
1521
|
-
? `\n${bgLine(`${TOOL_RESULT_INDENT}${theme.fg("muted", `… ${rem} more lines`)}`, width)}`
|
|
1522
|
-
: "";
|
|
1604
|
+
const moreLine = rem > 0 ? `\n${bgLine(`${TOOL_RESULT_INDENT}${theme.fg("muted", `… ${rem} more lines`)}`, width)}` : "";
|
|
1523
1605
|
return `${newHdr}\n${padDiffBody(preview)}${moreLine}`;
|
|
1524
1606
|
},
|
|
1525
1607
|
};
|
|
1526
1608
|
}
|
|
1527
1609
|
return text;
|
|
1528
1610
|
}
|
|
1611
|
+
clearToolHeaderBg(text);
|
|
1529
1612
|
text.setText(`${TOOL_RESULT_INDENT}${theme.fg("dim", String(result?.content?.[0]?.text ?? "written").slice(0, 120))}`);
|
|
1530
1613
|
return text;
|
|
1531
1614
|
},
|
|
@@ -1538,29 +1621,13 @@ export default async function diffRendererExtension(pi) {
|
|
|
1538
1621
|
if (Array.isArray(input?.edits)) {
|
|
1539
1622
|
return input.edits
|
|
1540
1623
|
.map((edit) => ({
|
|
1541
|
-
oldText: typeof edit?.oldText === "string"
|
|
1542
|
-
|
|
1543
|
-
: typeof edit?.old_text === "string"
|
|
1544
|
-
? edit.old_text
|
|
1545
|
-
: "",
|
|
1546
|
-
newText: typeof edit?.newText === "string"
|
|
1547
|
-
? edit.newText
|
|
1548
|
-
: typeof edit?.new_text === "string"
|
|
1549
|
-
? edit.new_text
|
|
1550
|
-
: "",
|
|
1624
|
+
oldText: typeof edit?.oldText === "string" ? edit.oldText : typeof edit?.old_text === "string" ? edit.old_text : "",
|
|
1625
|
+
newText: typeof edit?.newText === "string" ? edit.newText : typeof edit?.new_text === "string" ? edit.new_text : "",
|
|
1551
1626
|
}))
|
|
1552
1627
|
.filter((edit) => edit.oldText && edit.oldText !== edit.newText);
|
|
1553
1628
|
}
|
|
1554
|
-
const oldText = typeof input?.oldText === "string"
|
|
1555
|
-
|
|
1556
|
-
: typeof input?.old_text === "string"
|
|
1557
|
-
? input.old_text
|
|
1558
|
-
: "";
|
|
1559
|
-
const newText = typeof input?.newText === "string"
|
|
1560
|
-
? input.newText
|
|
1561
|
-
: typeof input?.new_text === "string"
|
|
1562
|
-
? input.new_text
|
|
1563
|
-
: "";
|
|
1629
|
+
const oldText = typeof input?.oldText === "string" ? input.oldText : typeof input?.old_text === "string" ? input.old_text : "";
|
|
1630
|
+
const newText = typeof input?.newText === "string" ? input.newText : typeof input?.new_text === "string" ? input.new_text : "";
|
|
1564
1631
|
return oldText && oldText !== newText ? [{ oldText, newText }] : [];
|
|
1565
1632
|
}
|
|
1566
1633
|
function summarizeEditOperations(operations) {
|
|
@@ -1580,57 +1647,21 @@ export default async function diffRendererExtension(pi) {
|
|
|
1580
1647
|
parameters: {
|
|
1581
1648
|
...(origEdit.parameters || {}),
|
|
1582
1649
|
properties: {
|
|
1583
|
-
...(
|
|
1584
|
-
|
|
1585
|
-
type: "
|
|
1586
|
-
description: "
|
|
1650
|
+
...(origEdit.parameters?.properties || {}),
|
|
1651
|
+
oldText: {
|
|
1652
|
+
type: "string",
|
|
1653
|
+
description: "Exact existing text to replace.",
|
|
1587
1654
|
},
|
|
1588
|
-
|
|
1589
|
-
type: "
|
|
1590
|
-
description: "
|
|
1591
|
-
items: {
|
|
1592
|
-
type: "object",
|
|
1593
|
-
properties: {
|
|
1594
|
-
hash_range_inclusive: {
|
|
1595
|
-
type: "array",
|
|
1596
|
-
items: { type: "string" },
|
|
1597
|
-
minItems: 2,
|
|
1598
|
-
maxItems: 2,
|
|
1599
|
-
description: "[start_anchor, end_anchor] from a prior hashline_read. Use identical anchor for a single-line edit.",
|
|
1600
|
-
},
|
|
1601
|
-
content_lines: {
|
|
1602
|
-
type: "array",
|
|
1603
|
-
items: { type: "string" },
|
|
1604
|
-
description: "Replacement lines. Pass an empty array to delete the range.",
|
|
1605
|
-
},
|
|
1606
|
-
},
|
|
1607
|
-
required: ["hash_range_inclusive", "content_lines"],
|
|
1608
|
-
additionalProperties: false,
|
|
1609
|
-
},
|
|
1655
|
+
newText: {
|
|
1656
|
+
type: "string",
|
|
1657
|
+
description: "Replacement text for oldText.",
|
|
1610
1658
|
},
|
|
1611
1659
|
},
|
|
1660
|
+
required: ["path"],
|
|
1661
|
+
additionalProperties: true,
|
|
1612
1662
|
},
|
|
1613
1663
|
async execute(tid, params, sig, upd, ctx) {
|
|
1614
1664
|
const fp = params.path ?? params.file_path ?? "";
|
|
1615
|
-
// Hashline path: strict, no fuzzy fallback, atomic write.
|
|
1616
|
-
// If the model uses hashlineChanges, route exclusively here. Otherwise fall through
|
|
1617
|
-
// to the legacy 6-strategy oldString/newString path below.
|
|
1618
|
-
if (Array.isArray(params.hashlineChanges) && params.hashlineChanges.length > 0) {
|
|
1619
|
-
if (!fp) {
|
|
1620
|
-
return {
|
|
1621
|
-
content: [{ type: "text", text: "[E_BAD_INPUT] path required for hashlineChanges" }],
|
|
1622
|
-
isError: true,
|
|
1623
|
-
};
|
|
1624
|
-
}
|
|
1625
|
-
const resolvedFp = resolve(cwd, fp);
|
|
1626
|
-
return runHashlineEdit({
|
|
1627
|
-
resolvedPath: resolvedFp,
|
|
1628
|
-
changes: params.hashlineChanges,
|
|
1629
|
-
dryRun: params.dryRun === true,
|
|
1630
|
-
toolCallId: tid,
|
|
1631
|
-
onDiffStats: (id, diff) => stashHashlineEditHeaderStats(id, diff.lines.length, diff.added, diff.removed),
|
|
1632
|
-
});
|
|
1633
|
-
}
|
|
1634
1665
|
const operations = getEditOperations(params);
|
|
1635
1666
|
// Try cascading replace() first — smarter matching than SDK's exact-only edit
|
|
1636
1667
|
if (fp && operations.length > 0 && existsSync(fp)) {
|
|
@@ -1665,9 +1696,7 @@ export default async function diffRendererExtension(pi) {
|
|
|
1665
1696
|
editLine = 0;
|
|
1666
1697
|
}
|
|
1667
1698
|
const useFull = !!params._expandGaps;
|
|
1668
|
-
const diffData = useFull
|
|
1669
|
-
? parseDiff(operations[0].oldText, operations[0].newText, undefined)
|
|
1670
|
-
: diffs[0];
|
|
1699
|
+
const diffData = useFull ? parseDiff(operations[0].oldText, operations[0].newText, undefined) : diffs[0];
|
|
1671
1700
|
return {
|
|
1672
1701
|
content: [{ type: "text", text: `Edited ${sp(fp)}` }],
|
|
1673
1702
|
details: {
|
|
@@ -1749,9 +1778,7 @@ export default async function diffRendererExtension(pi) {
|
|
|
1749
1778
|
editLine = 0;
|
|
1750
1779
|
}
|
|
1751
1780
|
const useFull = !!params._expandGaps;
|
|
1752
|
-
const diffData = useFull
|
|
1753
|
-
? parseDiff(operations[0].oldText, operations[0].newText, undefined)
|
|
1754
|
-
: diffs[0];
|
|
1781
|
+
const diffData = useFull ? parseDiff(operations[0].oldText, operations[0].newText, undefined) : diffs[0];
|
|
1755
1782
|
result.details = {
|
|
1756
1783
|
_type: "editInfo",
|
|
1757
1784
|
summary: editLine > 0 ? `${summary} at line ${editLine}` : summary,
|
|
@@ -1826,13 +1853,13 @@ export default async function diffRendererExtension(pi) {
|
|
|
1826
1853
|
catch {
|
|
1827
1854
|
previewLine = 0;
|
|
1828
1855
|
}
|
|
1829
|
-
const loc = previewLine > 0 ?
|
|
1830
|
-
text
|
|
1856
|
+
const loc = previewLine > 0 ? `${TOOL_RESULT_INDENT}${theme.fg("muted", `at line ${previewLine}`)}` : "";
|
|
1857
|
+
setToolHeaderBg(text);
|
|
1858
|
+
text.setText(formatToolFrameHeaderText({
|
|
1831
1859
|
label: "edit",
|
|
1832
1860
|
filePath: fp,
|
|
1833
1861
|
theme,
|
|
1834
|
-
|
|
1835
|
-
suffix: `${stats}${loc}`,
|
|
1862
|
+
suffix: loc,
|
|
1836
1863
|
topPad: EDIT_DIFF_RESULT_FRAME.topPad,
|
|
1837
1864
|
bottomPad: EDIT_DIFF_RESULT_FRAME.bottomPad,
|
|
1838
1865
|
headerLeftPad: EDIT_DIFF_RESULT_FRAME.headerLeftPad,
|
|
@@ -1865,17 +1892,23 @@ export default async function diffRendererExtension(pi) {
|
|
|
1865
1892
|
}
|
|
1866
1893
|
const d = result.details;
|
|
1867
1894
|
if (d?._type === "editInfo" && d.diff) {
|
|
1868
|
-
setDiffPreviewTask(text, "ed", "", d.diff, d.language, MAX_PREVIEW_LINES, theme, ctx, {
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1895
|
+
setDiffPreviewTask(text, "ed", "", d.diff, d.language, MAX_PREVIEW_LINES, theme, ctx, {
|
|
1896
|
+
omitHeader: true,
|
|
1897
|
+
previewBottomPad: EDIT_DIFF_RESULT_FRAME.previewBottomPad,
|
|
1898
|
+
compactGutter: true,
|
|
1899
|
+
bodyLeftPad: EDIT_DIFF_RESULT_FRAME.bodyLeftPad,
|
|
1900
|
+
});
|
|
1873
1901
|
return text;
|
|
1874
1902
|
}
|
|
1875
1903
|
if (d?._type === "multiEditInfo") {
|
|
1876
1904
|
const { editCount, diffLineCount, diff, language } = d;
|
|
1877
1905
|
if (diff) {
|
|
1878
|
-
setDiffPreviewTask(text, "me", "", diff, language, MAX_PREVIEW_LINES, theme, ctx, {
|
|
1906
|
+
setDiffPreviewTask(text, "me", "", diff, language, MAX_PREVIEW_LINES, theme, ctx, {
|
|
1907
|
+
omitHeader: true,
|
|
1908
|
+
previewBottomPad: EDIT_DIFF_RESULT_FRAME.previewBottomPad,
|
|
1909
|
+
compactGutter: true,
|
|
1910
|
+
bodyLeftPad: EDIT_DIFF_RESULT_FRAME.bodyLeftPad,
|
|
1911
|
+
});
|
|
1879
1912
|
return text;
|
|
1880
1913
|
}
|
|
1881
1914
|
const meta = `${editCount} edits${diffLineCountLabel(diffLineCount, theme)}`;
|
|
@@ -1883,172 +1916,94 @@ export default async function diffRendererExtension(pi) {
|
|
|
1883
1916
|
return text;
|
|
1884
1917
|
}
|
|
1885
1918
|
text.__piDiffTask = undefined;
|
|
1919
|
+
clearToolHeaderBg(text);
|
|
1886
1920
|
text.setText(`${TOOL_RESULT_INDENT}${theme.fg("dim", String(result?.content?.[0]?.text ?? "edited").slice(0, 120))}`);
|
|
1887
1921
|
return text;
|
|
1888
1922
|
},
|
|
1889
1923
|
});
|
|
1890
|
-
// Eager-init the hashline engine on session start so first edit has no WASM latency.
|
|
1891
|
-
pi.on("session_start", async () => {
|
|
1892
|
-
try {
|
|
1893
|
-
await initHashline();
|
|
1894
|
-
}
|
|
1895
|
-
catch {
|
|
1896
|
-
// best-effort; lazy init still works on first edit
|
|
1897
|
-
}
|
|
1898
|
-
});
|
|
1899
1924
|
pi.registerTool({
|
|
1900
|
-
name: "
|
|
1901
|
-
label: "
|
|
1902
|
-
description:
|
|
1925
|
+
name: "apply_patch",
|
|
1926
|
+
label: "apply_patch",
|
|
1927
|
+
description: "Multi-file patch engine. One call can add, update, delete, or move multiple files. Uses structured JSON changes array.",
|
|
1903
1928
|
parameters: {
|
|
1904
1929
|
type: "object",
|
|
1905
1930
|
properties: {
|
|
1906
|
-
|
|
1907
|
-
startLine: { type: "number", description: "Optional 1-indexed start line" },
|
|
1908
|
-
endLine: { type: "number", description: "Optional 1-indexed end line (inclusive)" },
|
|
1909
|
-
},
|
|
1910
|
-
required: ["path"],
|
|
1911
|
-
additionalProperties: false,
|
|
1912
|
-
},
|
|
1913
|
-
renderCall(args, theme, ctx) {
|
|
1914
|
-
const fp = args?.path ?? "";
|
|
1915
|
-
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
1916
|
-
resolvePreviewDiffColors(theme);
|
|
1917
|
-
const range = typeof args?.startLine === "number" || typeof args?.endLine === "number"
|
|
1918
|
-
? ` ${theme.fg("muted", `lines ${args.startLine ?? 1}-${args.endLine ?? "…"}`)}`
|
|
1919
|
-
: "";
|
|
1920
|
-
text.setText(formatToolFrameHeader({
|
|
1921
|
-
label: "hashline_read",
|
|
1922
|
-
filePath: fp,
|
|
1923
|
-
theme,
|
|
1924
|
-
width: termW(),
|
|
1925
|
-
suffix: range,
|
|
1926
|
-
topPad: 0,
|
|
1927
|
-
bottomPad: HASHLINE_TOOL_RESULT_FRAME.callTitleBottomPad,
|
|
1928
|
-
}));
|
|
1929
|
-
return text;
|
|
1930
|
-
},
|
|
1931
|
-
async execute(_tid, params) {
|
|
1932
|
-
await initHashline();
|
|
1933
|
-
const fp = resolve(cwd, params.path);
|
|
1934
|
-
if (!fp) {
|
|
1935
|
-
return { content: [{ type: "text", text: "[E_BAD_INPUT] path required" }], isError: true, details: {} };
|
|
1936
|
-
}
|
|
1937
|
-
let content;
|
|
1938
|
-
try {
|
|
1939
|
-
content = await fs.promises.readFile(fp, "utf8");
|
|
1940
|
-
}
|
|
1941
|
-
catch (err) {
|
|
1942
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1943
|
-
return {
|
|
1944
|
-
content: [{ type: "text", text: `[E_READ_FAILED] cannot read ${fp}: ${msg}` }],
|
|
1945
|
-
isError: true,
|
|
1946
|
-
details: {},
|
|
1947
|
-
};
|
|
1948
|
-
}
|
|
1949
|
-
const startLine = typeof params.startLine === "number" ? Math.max(1, params.startLine) : 1;
|
|
1950
|
-
const endLine = typeof params.endLine === "number" ? Math.max(startLine, params.endLine) : Infinity;
|
|
1951
|
-
return executeHashlineRead(fp, content, startLine, endLine);
|
|
1952
|
-
},
|
|
1953
|
-
renderResult(result, _opt, theme, ctx) {
|
|
1954
|
-
const text = getWidthAwareText(ctx.lastComponent);
|
|
1955
|
-
const d = result.details;
|
|
1956
|
-
const lineCount = d?.lineCount ?? 0;
|
|
1957
|
-
if (ctx.expanded === false) {
|
|
1958
|
-
text.__piDiffTask = undefined;
|
|
1959
|
-
const hint = theme.fg("dim", `(${lineCount} lines) ctrl+o to expand`);
|
|
1960
|
-
text.setText(hint);
|
|
1961
|
-
return text;
|
|
1962
|
-
}
|
|
1963
|
-
text.__piDiffTask = undefined;
|
|
1964
|
-
const out = (result.content || []).map((c) => c.text).join("\n");
|
|
1965
|
-
text.setText(out);
|
|
1966
|
-
return text;
|
|
1967
|
-
},
|
|
1968
|
-
});
|
|
1969
|
-
pi.registerTool({
|
|
1970
|
-
name: "hashline_edit",
|
|
1971
|
-
label: "hashline_edit",
|
|
1972
|
-
description: HASHLINE_EDIT_DESC,
|
|
1973
|
-
parameters: {
|
|
1974
|
-
type: "object",
|
|
1975
|
-
properties: {
|
|
1976
|
-
path: { type: "string", description: "Absolute or relative file path" },
|
|
1977
|
-
dryRun: {
|
|
1978
|
-
type: "boolean",
|
|
1979
|
-
description: "If true, validate anchors and return diff without writing the file.",
|
|
1980
|
-
},
|
|
1981
|
-
hashlineChanges: {
|
|
1931
|
+
changes: {
|
|
1982
1932
|
type: "array",
|
|
1983
|
-
description: "
|
|
1933
|
+
description: "Array of file changes to apply atomically.",
|
|
1984
1934
|
items: {
|
|
1985
1935
|
type: "object",
|
|
1986
1936
|
properties: {
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
maxItems: 2,
|
|
1993
|
-
},
|
|
1994
|
-
content_lines: {
|
|
1995
|
-
type: "array",
|
|
1996
|
-
description: "Replacement lines; use [] to delete the anchored range.",
|
|
1997
|
-
items: { type: "string" },
|
|
1937
|
+
path: { type: "string", description: "Absolute path to the file." },
|
|
1938
|
+
action: {
|
|
1939
|
+
type: "string",
|
|
1940
|
+
enum: ["add", "update", "delete", "move"],
|
|
1941
|
+
description: "The operation to perform.",
|
|
1998
1942
|
},
|
|
1943
|
+
content: { type: "string", description: "Content for new files (action=add)." },
|
|
1944
|
+
oldText: { type: "string", description: "Text to find for updates (action=update)." },
|
|
1945
|
+
newText: { type: "string", description: "Replacement text for updates (action=update)." },
|
|
1946
|
+
movePath: { type: "string", description: "Destination path for moves (action=move)." },
|
|
1999
1947
|
},
|
|
2000
|
-
required: ["
|
|
2001
|
-
additionalProperties: false,
|
|
1948
|
+
required: ["path", "action"],
|
|
2002
1949
|
},
|
|
2003
1950
|
},
|
|
2004
1951
|
},
|
|
2005
|
-
required: ["
|
|
2006
|
-
|
|
1952
|
+
required: ["changes"],
|
|
1953
|
+
},
|
|
1954
|
+
async execute(_tid, params) {
|
|
1955
|
+
const changes = (params.changes ?? []).map((c) => ({
|
|
1956
|
+
path: c.path,
|
|
1957
|
+
action: c.action,
|
|
1958
|
+
content: c.content,
|
|
1959
|
+
oldText: c.oldText,
|
|
1960
|
+
newText: c.newText,
|
|
1961
|
+
movePath: c.movePath,
|
|
1962
|
+
}));
|
|
1963
|
+
const result = await executeApplyPatch(changes);
|
|
1964
|
+
const output = formatApplyPatchResult(result);
|
|
1965
|
+
return {
|
|
1966
|
+
content: [{ type: "text", text: output }],
|
|
1967
|
+
isError: !result.ok,
|
|
1968
|
+
details: { _type: "applyPatchInfo", result },
|
|
1969
|
+
};
|
|
2007
1970
|
},
|
|
2008
1971
|
renderCall(args, theme, ctx) {
|
|
2009
|
-
const
|
|
2010
|
-
const
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
1972
|
+
const text = getWidthAwareText(ctx.lastComponent);
|
|
1973
|
+
const changes = Array.isArray(args?.changes) ? args.changes : [];
|
|
1974
|
+
const count = changes.length;
|
|
1975
|
+
if (ctx.argsComplete && count > 0) {
|
|
1976
|
+
clearToolHeaderBg(text);
|
|
1977
|
+
text.setText("");
|
|
1978
|
+
return text;
|
|
1979
|
+
}
|
|
1980
|
+
const suffix = count
|
|
1981
|
+
? `${TOOL_RESULT_INDENT}${theme.fg("muted", `(${count} change${count === 1 ? "" : "s"})`)}${TOOL_RESULT_INDENT}${summarizeApplyPatchChanges(changes, theme)}`
|
|
1982
|
+
: `${TOOL_RESULT_INDENT}${theme.fg("muted", "(waiting for changes)")}`;
|
|
1983
|
+
setToolHeaderBg(text);
|
|
1984
|
+
text.setText(formatToolFrameHeaderText({
|
|
1985
|
+
meta: `${theme.fg("toolTitle", theme.bold("apply_patch"))}${suffix}`,
|
|
2019
1986
|
topPad: 0,
|
|
2020
|
-
bottomPad:
|
|
1987
|
+
bottomPad: 0,
|
|
2021
1988
|
}));
|
|
2022
1989
|
return text;
|
|
2023
1990
|
},
|
|
2024
|
-
async execute(tid, params) {
|
|
2025
|
-
await initHashline();
|
|
2026
|
-
const fp = resolve(cwd, params.path);
|
|
2027
|
-
if (!fp) {
|
|
2028
|
-
return { content: [{ type: "text", text: "[E_BAD_INPUT] path required" }], isError: true, details: {} };
|
|
2029
|
-
}
|
|
2030
|
-
if (!Array.isArray(params.hashlineChanges) || params.hashlineChanges.length === 0) {
|
|
2031
|
-
return { content: [{ type: "text", text: "[E_EMPTY] hashlineChanges must be a non-empty array" }], isError: true, details: {} };
|
|
2032
|
-
}
|
|
2033
|
-
return runHashlineEdit({
|
|
2034
|
-
resolvedPath: fp,
|
|
2035
|
-
changes: params.hashlineChanges,
|
|
2036
|
-
dryRun: params.dryRun === true,
|
|
2037
|
-
toolCallId: tid,
|
|
2038
|
-
onDiffStats: (id, diff) => stashHashlineEditHeaderStats(id, diff.lines.length, diff.added, diff.removed),
|
|
2039
|
-
});
|
|
2040
|
-
},
|
|
2041
1991
|
renderResult(result, _opt, theme, ctx) {
|
|
2042
1992
|
const text = getWidthAwareText(ctx.lastComponent);
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
1993
|
+
if (ctx.isError) {
|
|
1994
|
+
const out = (result.content || []).map((c) => c.text).join("\n") || "Error";
|
|
1995
|
+
text.__piDiffTask = undefined;
|
|
1996
|
+
clearToolHeaderBg(text);
|
|
1997
|
+
text.setText(theme.fg("error", out));
|
|
2046
1998
|
return text;
|
|
2047
1999
|
}
|
|
2048
|
-
|
|
2049
|
-
text
|
|
2000
|
+
const patchResult = result.details?.result;
|
|
2001
|
+
if (renderApplyPatchPreview(text, patchResult, theme, ctx))
|
|
2002
|
+
return text;
|
|
2050
2003
|
const out = (result.content || []).map((c) => c.text).join("\n");
|
|
2051
|
-
text.
|
|
2004
|
+
text.__piDiffTask = undefined;
|
|
2005
|
+
clearToolHeaderBg(text);
|
|
2006
|
+
text.setText(theme.fg("muted", out));
|
|
2052
2007
|
return text;
|
|
2053
2008
|
},
|
|
2054
2009
|
});
|