@heyhuynhgiabuu/pi-diff 0.4.1 → 0.5.1
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/README.md +101 -53
- package/dist/cli.js +4 -6
- package/dist/cli.js.map +1 -1
- package/dist/core/diff.js +2 -38
- package/dist/core/diff.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +127 -272
- package/dist/index.js.map +1 -1
- package/dist/review/command.d.ts +2 -1
- package/dist/review/command.d.ts.map +1 -1
- package/dist/review/command.js +252 -174
- package/dist/review/command.js.map +1 -1
- package/dist/review/export.js +1 -4
- package/dist/review/export.js.map +1 -1
- package/dist/review/file-preview.js +7 -10
- package/dist/review/file-preview.js.map +1 -1
- package/dist/review/git.js +14 -22
- package/dist/review/git.js.map +1 -1
- package/dist/review/hunk-bridge.d.ts +54 -0
- package/dist/review/hunk-bridge.d.ts.map +1 -0
- package/dist/review/hunk-bridge.js +105 -0
- package/dist/review/hunk-bridge.js.map +1 -0
- package/dist/review/hunk-preview.js +16 -58
- package/dist/review/hunk-preview.js.map +1 -1
- package/dist/review/interactive.js +3 -8
- package/dist/review/interactive.js.map +1 -1
- package/dist/review/model.js +9 -20
- package/dist/review/model.js.map +1 -1
- package/dist/review/prompt.js +3 -6
- package/dist/review/prompt.js.map +1 -1
- package/dist/review/session.js +22 -44
- package/dist/review/session.js.map +1 -1
- package/dist/review/tui.js +47 -51
- package/dist/review/tui.js.map +1 -1
- package/package.json +11 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* pi-diff — Shiki-powered terminal diff renderer for pi.
|
|
4
3
|
*
|
|
@@ -21,52 +20,16 @@
|
|
|
21
20
|
* • Large-diff fallback (skip highlighting, still show diff)
|
|
22
21
|
* • Async rendering with invalidate() for non-blocking preview
|
|
23
22
|
*/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}));
|
|
35
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
36
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
37
|
-
}) : function(o, v) {
|
|
38
|
-
o["default"] = v;
|
|
39
|
-
});
|
|
40
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
41
|
-
var ownKeys = function(o) {
|
|
42
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
43
|
-
var ar = [];
|
|
44
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
45
|
-
return ar;
|
|
46
|
-
};
|
|
47
|
-
return ownKeys(o);
|
|
48
|
-
};
|
|
49
|
-
return function (mod) {
|
|
50
|
-
if (mod && mod.__esModule) return mod;
|
|
51
|
-
var result = {};
|
|
52
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
53
|
-
__setModuleDefault(result, mod);
|
|
54
|
-
return result;
|
|
55
|
-
};
|
|
56
|
-
})();
|
|
57
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
58
|
-
exports.__testing = void 0;
|
|
59
|
-
exports.default = diffRendererExtension;
|
|
60
|
-
const node_fs_1 = require("node:fs");
|
|
61
|
-
const node_path_1 = require("node:path");
|
|
62
|
-
const cli_1 = require("@shikijs/cli");
|
|
63
|
-
const Diff = __importStar(require("diff"));
|
|
64
|
-
const diff_js_1 = require("./core/diff.js");
|
|
65
|
-
const command_js_1 = require("./review/command.js");
|
|
66
|
-
const export_js_1 = require("./review/export.js");
|
|
67
|
-
const git_js_1 = require("./review/git.js");
|
|
68
|
-
const hunk_preview_js_1 = require("./review/hunk-preview.js");
|
|
69
|
-
const interactive_js_1 = require("./review/interactive.js");
|
|
23
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
24
|
+
import { extname, relative } from "node:path";
|
|
25
|
+
import { codeToANSI } from "@shikijs/cli";
|
|
26
|
+
import * as Diff from "diff";
|
|
27
|
+
import { parseDiff } from "./core/diff.js";
|
|
28
|
+
import { registerReviewDiffCommand } from "./review/command.js";
|
|
29
|
+
import { formatReviewMarkdown } from "./review/export.js";
|
|
30
|
+
import { countReviewDiffLines, readGitDiff } from "./review/git.js";
|
|
31
|
+
import { applyDiffPalette as applySharedDiffPalette, lang as detectDiffLanguage, renderSplit as renderSharedSplit, resolveDiffColors as resolveSharedDiffColors, themeCacheKey as sharedThemeCacheKey, } from "./review/hunk-preview.js";
|
|
32
|
+
import { formatInteractiveReviewPanel } from "./review/interactive.js";
|
|
70
33
|
const DIFF_PRESETS = {
|
|
71
34
|
default: {
|
|
72
35
|
name: "default",
|
|
@@ -245,8 +208,8 @@ function loadDiffConfig() {
|
|
|
245
208
|
const paths = [`${process.cwd()}/.pi/settings.json`, `${process.env.HOME ?? ""}/.pi/settings.json`];
|
|
246
209
|
for (const p of paths) {
|
|
247
210
|
try {
|
|
248
|
-
if (
|
|
249
|
-
const raw = JSON.parse(
|
|
211
|
+
if (existsSync(p)) {
|
|
212
|
+
const raw = JSON.parse(readFileSync(p, "utf-8"));
|
|
250
213
|
if (raw.diffTheme || raw.diffColors) {
|
|
251
214
|
return { diffTheme: raw.diffTheme, diffColors: raw.diffColors };
|
|
252
215
|
}
|
|
@@ -681,7 +644,7 @@ function lnum(n, w, fg = FG_LNUM) {
|
|
|
681
644
|
function shortPath(cwd, home, p) {
|
|
682
645
|
if (!p)
|
|
683
646
|
return "";
|
|
684
|
-
const r =
|
|
647
|
+
const r = relative(cwd, p);
|
|
685
648
|
if (!r.startsWith("..") && !r.startsWith("/"))
|
|
686
649
|
return r;
|
|
687
650
|
return p.replace(home, "~");
|
|
@@ -777,14 +740,14 @@ const EXT_LANG = {
|
|
|
777
740
|
vue: "vue",
|
|
778
741
|
};
|
|
779
742
|
function lang(fp) {
|
|
780
|
-
return EXT_LANG[
|
|
743
|
+
return EXT_LANG[extname(fp).slice(1).toLowerCase()];
|
|
781
744
|
}
|
|
782
745
|
// ---------------------------------------------------------------------------
|
|
783
746
|
// Shiki ANSI cache + pre-warm
|
|
784
747
|
// ---------------------------------------------------------------------------
|
|
785
748
|
// Pre-warm the Shiki singleton (loads WASM grammars + theme) so the first
|
|
786
749
|
// diff render doesn't pay the ~200-500ms startup cost.
|
|
787
|
-
|
|
750
|
+
codeToANSI("", "typescript", THEME).catch(() => { });
|
|
788
751
|
const _cache = new Map();
|
|
789
752
|
function _touch(k, v) {
|
|
790
753
|
_cache.delete(k);
|
|
@@ -807,7 +770,7 @@ async function hlBlock(code, language) {
|
|
|
807
770
|
if (hit)
|
|
808
771
|
return _touch(k, hit);
|
|
809
772
|
try {
|
|
810
|
-
const ansi = normalizeShikiContrast(await
|
|
773
|
+
const ansi = normalizeShikiContrast(await codeToANSI(code, language, THEME));
|
|
811
774
|
const out = (ansi.endsWith("\n") ? ansi.slice(0, -1) : ansi).split("\n");
|
|
812
775
|
return _touch(k, out);
|
|
813
776
|
}
|
|
@@ -1175,9 +1138,9 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1175
1138
|
// ---------------------------------------------------------------------------
|
|
1176
1139
|
// Extension
|
|
1177
1140
|
// ---------------------------------------------------------------------------
|
|
1178
|
-
|
|
1141
|
+
export const __testing = {
|
|
1179
1142
|
normalizeShikiContrast,
|
|
1180
|
-
parseDiff
|
|
1143
|
+
parseDiff,
|
|
1181
1144
|
renderSplit,
|
|
1182
1145
|
renderUnified,
|
|
1183
1146
|
};
|
|
@@ -1202,9 +1165,9 @@ function normalizeOptionalPositiveInteger(value, name) {
|
|
|
1202
1165
|
throw new Error(`${name} must be a positive integer`);
|
|
1203
1166
|
return number;
|
|
1204
1167
|
}
|
|
1205
|
-
async function diffRendererExtension(pi) {
|
|
1168
|
+
export default async function diffRendererExtension(pi) {
|
|
1206
1169
|
// Apply diff theme palette from settings/presets before rendering
|
|
1207
|
-
(
|
|
1170
|
+
applySharedDiffPalette();
|
|
1208
1171
|
let createWriteTool, createEditTool, getMarkdownTheme, TextComponent, MarkdownComponent;
|
|
1209
1172
|
try {
|
|
1210
1173
|
const sdk = await import("@earendil-works/pi-coding-agent");
|
|
@@ -1224,8 +1187,45 @@ async function diffRendererExtension(pi) {
|
|
|
1224
1187
|
const cwd = process.cwd();
|
|
1225
1188
|
const home = process.env.HOME ?? "";
|
|
1226
1189
|
const sp = (p) => shortPath(cwd, home, p);
|
|
1227
|
-
|
|
1228
|
-
|
|
1190
|
+
/** Wrap a Text component so its render(width) kicks off async diff rendering
|
|
1191
|
+
* using the real TUI width (which accounts for the sidebar). */
|
|
1192
|
+
function getWidthAwareText(lastComponent) {
|
|
1193
|
+
const text = (lastComponent ?? new TextComponent("", 0, 0));
|
|
1194
|
+
if (text.__piDiffWidthAware)
|
|
1195
|
+
return text;
|
|
1196
|
+
const baseRender = typeof text.render === "function" ? text.render.bind(text) : null;
|
|
1197
|
+
if (!baseRender)
|
|
1198
|
+
return text;
|
|
1199
|
+
text.__piDiffWidthAware = true;
|
|
1200
|
+
text.__piDiffRender = baseRender;
|
|
1201
|
+
text.render = (width) => {
|
|
1202
|
+
const task = text.__piDiffTask;
|
|
1203
|
+
if (task) {
|
|
1204
|
+
const renderWidth = Math.max(1, Math.floor(width || termW()));
|
|
1205
|
+
const key = task.key(renderWidth);
|
|
1206
|
+
if (text.__piDiffRenderedKey !== key) {
|
|
1207
|
+
text.__piDiffRenderedKey = key;
|
|
1208
|
+
text.setText(task.placeholder);
|
|
1209
|
+
Promise.resolve(task.render(renderWidth))
|
|
1210
|
+
.then((rendered) => {
|
|
1211
|
+
if (text.__piDiffRenderedKey !== key)
|
|
1212
|
+
return;
|
|
1213
|
+
text.setText(rendered);
|
|
1214
|
+
task.invalidate?.();
|
|
1215
|
+
})
|
|
1216
|
+
.catch(() => {
|
|
1217
|
+
if (text.__piDiffRenderedKey !== key)
|
|
1218
|
+
return;
|
|
1219
|
+
text.setText(task.fallback);
|
|
1220
|
+
task.invalidate?.();
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
return text.__piDiffRender(width);
|
|
1225
|
+
};
|
|
1226
|
+
return text;
|
|
1227
|
+
}
|
|
1228
|
+
registerReviewDiffCommand(pi, cwd);
|
|
1229
1229
|
pi.registerTool({
|
|
1230
1230
|
name: "review_git_diff",
|
|
1231
1231
|
label: "Review Git Diff",
|
|
@@ -1279,31 +1279,32 @@ Examples:
|
|
|
1279
1279
|
},
|
|
1280
1280
|
async execute(_tid, params = {}) {
|
|
1281
1281
|
try {
|
|
1282
|
-
const safeParams = params ?? {};
|
|
1283
|
-
const diff = await
|
|
1282
|
+
const safeParams = (params ?? {});
|
|
1283
|
+
const diff = await readGitDiff(cwd, reviewGitDiffMode(safeParams));
|
|
1284
1284
|
const maxLinesPerHunk = reviewGitDiffMaxLines(safeParams);
|
|
1285
1285
|
const markdown = safeParams.includeRawDiff || safeParams.raw
|
|
1286
|
-
?
|
|
1287
|
-
:
|
|
1286
|
+
? formatReviewMarkdown(diff, { includeRawDiff: true, maxLinesPerHunk })
|
|
1287
|
+
: formatInteractiveReviewPanel(diff, [], {
|
|
1288
1288
|
file: safeParams.file,
|
|
1289
1289
|
hunkId: safeParams.hunkId,
|
|
1290
1290
|
maxFiles: normalizeOptionalPositiveInteger(safeParams.maxFiles, "maxFiles"),
|
|
1291
1291
|
maxHunks: normalizeOptionalPositiveInteger(safeParams.maxHunks, "maxHunks"),
|
|
1292
1292
|
maxLinesPerHunk,
|
|
1293
1293
|
});
|
|
1294
|
-
const counts =
|
|
1294
|
+
const counts = countReviewDiffLines(diff);
|
|
1295
1295
|
return {
|
|
1296
1296
|
content: [{ type: "text", text: markdown }],
|
|
1297
1297
|
details: {
|
|
1298
1298
|
_type: "reviewGitDiff",
|
|
1299
1299
|
markdown,
|
|
1300
|
-
mode: diff.mode,
|
|
1300
|
+
mode: String(diff.mode),
|
|
1301
1301
|
fileCount: diff.files.length,
|
|
1302
1302
|
insertions: counts.insertions,
|
|
1303
1303
|
deletions: counts.deletions,
|
|
1304
|
-
commentCount:
|
|
1304
|
+
commentCount: 0,
|
|
1305
1305
|
focusedFile: safeParams.file,
|
|
1306
1306
|
focusedHunk: safeParams.hunkId,
|
|
1307
|
+
error: undefined,
|
|
1307
1308
|
},
|
|
1308
1309
|
};
|
|
1309
1310
|
}
|
|
@@ -1311,14 +1312,15 @@ Examples:
|
|
|
1311
1312
|
const message = error instanceof Error ? error.message : String(error);
|
|
1312
1313
|
return {
|
|
1313
1314
|
content: [{ type: "text", text: `Error: ${message}` }],
|
|
1314
|
-
details: { _type: "reviewGitDiff", error: message },
|
|
1315
|
+
details: { _type: "reviewGitDiff", markdown: "", mode: "error", fileCount: 0, insertions: 0, deletions: 0, commentCount: 0, focusedFile: undefined, focusedHunk: undefined, error: message },
|
|
1315
1316
|
};
|
|
1316
1317
|
}
|
|
1317
1318
|
},
|
|
1318
1319
|
renderCall(args, theme, ctx) {
|
|
1319
1320
|
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
1320
|
-
const
|
|
1321
|
-
const
|
|
1321
|
+
const safeArgs = args;
|
|
1322
|
+
const base = typeof safeArgs?.base === "string" && safeArgs.base.trim() ? ` vs ${safeArgs.base.trim()}` : " working tree";
|
|
1323
|
+
const focus = typeof safeArgs?.file === "string" && safeArgs.file.trim() ? ` • ${safeArgs.file.trim()}` : "";
|
|
1322
1324
|
text.setText(`${theme.fg("toolTitle", theme.bold("review_git_diff"))}${theme.fg("muted", `${base}${focus}`)}`);
|
|
1323
1325
|
return text;
|
|
1324
1326
|
},
|
|
@@ -1342,113 +1344,6 @@ Examples:
|
|
|
1342
1344
|
return text;
|
|
1343
1345
|
},
|
|
1344
1346
|
});
|
|
1345
|
-
pi.registerTool({
|
|
1346
|
-
name: "review_git_comment",
|
|
1347
|
-
label: "Draft Review Comment",
|
|
1348
|
-
description: "Draft an inline code-review comment for the current interactive Git review. This stores comments in memory for the current Pi session and does not modify files or submit anything externally.",
|
|
1349
|
-
promptSnippet: "Draft an inline comment for the current Git review session.",
|
|
1350
|
-
parameters: {
|
|
1351
|
-
type: "object",
|
|
1352
|
-
properties: {
|
|
1353
|
-
file: { type: "string", description: "Changed file path to comment on." },
|
|
1354
|
-
line: { type: "number", description: "Optional old/new line number to comment on." },
|
|
1355
|
-
hunkId: { type: "string", description: "Optional hunk id from review_git_diff output." },
|
|
1356
|
-
body: { type: "string", description: "Comment body." },
|
|
1357
|
-
},
|
|
1358
|
-
required: ["file", "body"],
|
|
1359
|
-
additionalProperties: false,
|
|
1360
|
-
},
|
|
1361
|
-
async execute(_tid, params) {
|
|
1362
|
-
const file = typeof params?.file === "string" ? params.file.trim() : "";
|
|
1363
|
-
const body = typeof params?.body === "string" ? params.body.trim() : "";
|
|
1364
|
-
if (!file)
|
|
1365
|
-
return {
|
|
1366
|
-
content: [{ type: "text", text: "Error: file is required" }],
|
|
1367
|
-
details: { error: "file required" },
|
|
1368
|
-
};
|
|
1369
|
-
if (!body)
|
|
1370
|
-
return {
|
|
1371
|
-
content: [{ type: "text", text: "Error: body is required" }],
|
|
1372
|
-
details: { error: "body required" },
|
|
1373
|
-
};
|
|
1374
|
-
const comment = (0, interactive_js_1.createReviewComment)({
|
|
1375
|
-
comments: reviewComments,
|
|
1376
|
-
file,
|
|
1377
|
-
body,
|
|
1378
|
-
line: normalizeOptionalPositiveInteger(params.line, "line"),
|
|
1379
|
-
hunkId: typeof params.hunkId === "string" && params.hunkId.trim() ? params.hunkId.trim() : undefined,
|
|
1380
|
-
});
|
|
1381
|
-
reviewComments.push(comment);
|
|
1382
|
-
return {
|
|
1383
|
-
content: [
|
|
1384
|
-
{
|
|
1385
|
-
type: "text",
|
|
1386
|
-
text: `Drafted ${comment.id} on ${comment.file}${comment.line ? `:${comment.line}` : ""}\n\n${comment.body}`,
|
|
1387
|
-
},
|
|
1388
|
-
],
|
|
1389
|
-
details: { _type: "reviewGitComment", comment, commentCount: reviewComments.length },
|
|
1390
|
-
};
|
|
1391
|
-
},
|
|
1392
|
-
renderCall(args, theme, ctx) {
|
|
1393
|
-
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
1394
|
-
text.setText(`${theme.fg("toolTitle", theme.bold("review_git_comment"))} ${theme.fg("accent", args?.file ?? "")}`);
|
|
1395
|
-
return text;
|
|
1396
|
-
},
|
|
1397
|
-
renderResult(result, _opt, theme, ctx) {
|
|
1398
|
-
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
1399
|
-
if (ctx.isError || result.details?.error) {
|
|
1400
|
-
text.setText(`\n${theme.fg("error", result.details?.error ?? "review_git_comment failed")}`);
|
|
1401
|
-
return text;
|
|
1402
|
-
}
|
|
1403
|
-
const comment = result.details?.comment;
|
|
1404
|
-
text.setText(` ${theme.fg("success", `✓ drafted ${comment?.id ?? "comment"}`)}${theme.fg("muted", ` (${result.details?.commentCount ?? 0} total)`)}`);
|
|
1405
|
-
return text;
|
|
1406
|
-
},
|
|
1407
|
-
});
|
|
1408
|
-
pi.registerTool({
|
|
1409
|
-
name: "review_git_comments",
|
|
1410
|
-
label: "Review Comments",
|
|
1411
|
-
description: "List or clear drafted interactive Git review comments for the current Pi session.",
|
|
1412
|
-
promptSnippet: "List or clear drafted review comments.",
|
|
1413
|
-
parameters: {
|
|
1414
|
-
type: "object",
|
|
1415
|
-
properties: {
|
|
1416
|
-
clear: { type: "boolean", description: "Clear all drafted comments when true." },
|
|
1417
|
-
},
|
|
1418
|
-
additionalProperties: false,
|
|
1419
|
-
},
|
|
1420
|
-
async execute(_tid, params = {}) {
|
|
1421
|
-
if (params?.clear) {
|
|
1422
|
-
const cleared = reviewComments.length;
|
|
1423
|
-
reviewComments.length = 0;
|
|
1424
|
-
return {
|
|
1425
|
-
content: [{ type: "text", text: `Cleared ${cleared} drafted review comments.` }],
|
|
1426
|
-
details: { _type: "reviewGitComments", cleared, commentCount: 0 },
|
|
1427
|
-
};
|
|
1428
|
-
}
|
|
1429
|
-
const markdown = (0, interactive_js_1.formatReviewComments)(reviewComments);
|
|
1430
|
-
return {
|
|
1431
|
-
content: [{ type: "text", text: markdown }],
|
|
1432
|
-
details: { _type: "reviewGitComments", markdown, commentCount: reviewComments.length },
|
|
1433
|
-
};
|
|
1434
|
-
},
|
|
1435
|
-
renderCall(_args, theme, ctx) {
|
|
1436
|
-
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
1437
|
-
text.setText(theme.fg("toolTitle", theme.bold("review_git_comments")));
|
|
1438
|
-
return text;
|
|
1439
|
-
},
|
|
1440
|
-
renderResult(result, _opt, theme, ctx) {
|
|
1441
|
-
if (MarkdownComponent && getMarkdownTheme && typeof result.details?.markdown === "string") {
|
|
1442
|
-
return new MarkdownComponent(result.details.markdown, 0, 0, getMarkdownTheme());
|
|
1443
|
-
}
|
|
1444
|
-
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
1445
|
-
const cleared = typeof result.details?.cleared === "number"
|
|
1446
|
-
? `cleared ${result.details.cleared}`
|
|
1447
|
-
: `${result.details?.commentCount ?? 0} drafted`;
|
|
1448
|
-
text.setText(` ${theme.fg("muted", cleared)} review comments`);
|
|
1449
|
-
return text;
|
|
1450
|
-
},
|
|
1451
|
-
});
|
|
1452
1347
|
// =======================================================================
|
|
1453
1348
|
// write
|
|
1454
1349
|
// =======================================================================
|
|
@@ -1460,8 +1355,8 @@ Examples:
|
|
|
1460
1355
|
const fp = params.path ?? params.file_path ?? "";
|
|
1461
1356
|
let old = null;
|
|
1462
1357
|
try {
|
|
1463
|
-
if (fp &&
|
|
1464
|
-
old =
|
|
1358
|
+
if (fp && existsSync(fp))
|
|
1359
|
+
old = readFileSync(fp, "utf-8");
|
|
1465
1360
|
}
|
|
1466
1361
|
catch {
|
|
1467
1362
|
old = null;
|
|
@@ -1470,8 +1365,8 @@ Examples:
|
|
|
1470
1365
|
const content = params.content ?? "";
|
|
1471
1366
|
// Store in details — the only custom field TUI preserves in renderResult
|
|
1472
1367
|
if (old !== null && old !== content) {
|
|
1473
|
-
const diff =
|
|
1474
|
-
const lg = (
|
|
1368
|
+
const diff = parseDiff(old, content);
|
|
1369
|
+
const lg = detectDiffLanguage(fp);
|
|
1475
1370
|
result.details = { _type: "diff", summary: summarize(diff.added, diff.removed), diff, language: lg };
|
|
1476
1371
|
}
|
|
1477
1372
|
else if (old === null) {
|
|
@@ -1485,7 +1380,7 @@ Examples:
|
|
|
1485
1380
|
},
|
|
1486
1381
|
renderCall(args, theme, ctx) {
|
|
1487
1382
|
const fp = args?.path ?? args?.file_path ?? "";
|
|
1488
|
-
const isNew = !fp || !
|
|
1383
|
+
const isNew = !fp || !existsSync(fp);
|
|
1489
1384
|
const label = isNew ? "create" : "write";
|
|
1490
1385
|
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
1491
1386
|
const hdr = `${theme.fg("toolTitle", theme.bold(label))} ${theme.fg("accent", sp(fp))}`;
|
|
@@ -1497,11 +1392,11 @@ Examples:
|
|
|
1497
1392
|
}
|
|
1498
1393
|
// New file preview with Shiki
|
|
1499
1394
|
if (args?.content && ctx.argsComplete && isNew) {
|
|
1500
|
-
const previewKey = `create:${(
|
|
1395
|
+
const previewKey = `create:${sharedThemeCacheKey(theme)}:${fp}:${String(args.content).length}`;
|
|
1501
1396
|
if (ctx.state._previewKey !== previewKey) {
|
|
1502
1397
|
ctx.state._previewKey = previewKey;
|
|
1503
1398
|
ctx.state._previewText = hdr;
|
|
1504
|
-
const lg = (
|
|
1399
|
+
const lg = detectDiffLanguage(fp);
|
|
1505
1400
|
hlBlock(args.content, lg)
|
|
1506
1401
|
.then((lines) => {
|
|
1507
1402
|
if (ctx.state._previewKey !== previewKey)
|
|
@@ -1524,51 +1419,41 @@ Examples:
|
|
|
1524
1419
|
return text;
|
|
1525
1420
|
},
|
|
1526
1421
|
renderResult(result, _opt, theme, ctx) {
|
|
1527
|
-
const text = ctx.lastComponent
|
|
1422
|
+
const text = getWidthAwareText(ctx.lastComponent);
|
|
1528
1423
|
if (ctx.isError) {
|
|
1529
1424
|
const e = result.content
|
|
1530
1425
|
?.filter((c) => c.type === "text")
|
|
1531
1426
|
.map((c) => c.text || "")
|
|
1532
1427
|
.join("\n") ?? "Error";
|
|
1428
|
+
text.__piDiffTask = undefined;
|
|
1533
1429
|
text.setText(`\n${theme.fg("error", e)}`);
|
|
1534
1430
|
return text;
|
|
1535
1431
|
}
|
|
1536
1432
|
const d = result.details;
|
|
1537
1433
|
if (d?._type === "diff") {
|
|
1538
|
-
const
|
|
1539
|
-
const
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
(
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
return;
|
|
1548
|
-
ctx.state._wdt = ` ${d.summary}\n${rendered}`;
|
|
1549
|
-
ctx.invalidate();
|
|
1550
|
-
})
|
|
1551
|
-
.catch(() => {
|
|
1552
|
-
if (ctx.state._wdk !== key)
|
|
1553
|
-
return;
|
|
1554
|
-
ctx.state._wdt = ` ${d.summary}`;
|
|
1555
|
-
ctx.invalidate();
|
|
1556
|
-
});
|
|
1557
|
-
}
|
|
1558
|
-
text.setText(ctx.state._wdt ?? ` ${d.summary}`);
|
|
1434
|
+
const themeKey = sharedThemeCacheKey(theme);
|
|
1435
|
+
const dc = resolveSharedDiffColors(theme);
|
|
1436
|
+
text.__piDiffTask = {
|
|
1437
|
+
placeholder: ` ${d.summary}\n${theme.fg("muted", " rendering diff…")}`,
|
|
1438
|
+
fallback: ` ${d.summary}`,
|
|
1439
|
+
invalidate: ctx.invalidate,
|
|
1440
|
+
key: (width) => `wd:${themeKey}:${width}:${d.summary}:${d.diff?.lines?.length ?? 0}:${d.language ?? ""}`,
|
|
1441
|
+
render: async (width) => ` ${d.summary}\n${await renderSharedSplit(d.diff, d.language, MAX_RENDER_LINES, dc, width)}`,
|
|
1442
|
+
};
|
|
1559
1443
|
return text;
|
|
1560
1444
|
}
|
|
1561
1445
|
if (d?._type === "noChange") {
|
|
1446
|
+
text.__piDiffTask = undefined;
|
|
1562
1447
|
text.setText(` ${theme.fg("muted", "✓ no changes")}`);
|
|
1563
1448
|
return text;
|
|
1564
1449
|
}
|
|
1565
1450
|
if (d?._type === "new") {
|
|
1566
1451
|
const { lines: lineCount, content: rawContent, filePath: fp } = d;
|
|
1567
|
-
const pk = `nf:${(
|
|
1452
|
+
const pk = `nf:${sharedThemeCacheKey(theme)}:${fp}:${lineCount}`;
|
|
1568
1453
|
if (ctx.state._nfk !== pk) {
|
|
1569
1454
|
ctx.state._nfk = pk;
|
|
1570
1455
|
ctx.state._nft = ` ${theme.fg("success", `✓ new file (${lineCount} lines)`)}`;
|
|
1571
|
-
const lg = (
|
|
1456
|
+
const lg = detectDiffLanguage(fp);
|
|
1572
1457
|
if (rawContent) {
|
|
1573
1458
|
hlBlock(rawContent, lg)
|
|
1574
1459
|
.then((hlLines) => {
|
|
@@ -1611,7 +1496,7 @@ Examples:
|
|
|
1611
1496
|
return oldText && oldText !== newText ? [{ oldText, newText }] : [];
|
|
1612
1497
|
}
|
|
1613
1498
|
function summarizeEditOperations(operations) {
|
|
1614
|
-
const diffs = operations.map((edit) =>
|
|
1499
|
+
const diffs = operations.map((edit) => parseDiff(edit.oldText, edit.newText));
|
|
1615
1500
|
const totalAdded = diffs.reduce((sum, diff) => sum + diff.added, 0);
|
|
1616
1501
|
const totalRemoved = diffs.reduce((sum, diff) => sum + diff.removed, 0);
|
|
1617
1502
|
return {
|
|
@@ -1631,11 +1516,12 @@ Examples:
|
|
|
1631
1516
|
if (operations.length === 0)
|
|
1632
1517
|
return result;
|
|
1633
1518
|
const { diffs, summary } = summarizeEditOperations(operations);
|
|
1519
|
+
const lg = detectDiffLanguage(fp);
|
|
1634
1520
|
if (operations.length === 1) {
|
|
1635
1521
|
let editLine = 0;
|
|
1636
1522
|
try {
|
|
1637
|
-
if (fp &&
|
|
1638
|
-
const f =
|
|
1523
|
+
if (fp && existsSync(fp)) {
|
|
1524
|
+
const f = readFileSync(fp, "utf-8");
|
|
1639
1525
|
const idx = f.indexOf(operations[0].newText);
|
|
1640
1526
|
if (idx >= 0)
|
|
1641
1527
|
editLine = f.slice(0, idx).split("\n").length;
|
|
@@ -1644,7 +1530,7 @@ Examples:
|
|
|
1644
1530
|
catch {
|
|
1645
1531
|
editLine = 0;
|
|
1646
1532
|
}
|
|
1647
|
-
result.details = { _type: "editInfo", summary, editLine };
|
|
1533
|
+
result.details = { _type: "editInfo", summary, editLine, diff: diffs[0], language: lg };
|
|
1648
1534
|
return result;
|
|
1649
1535
|
}
|
|
1650
1536
|
result.details = {
|
|
@@ -1660,85 +1546,54 @@ Examples:
|
|
|
1660
1546
|
const operations = getEditOperations(args);
|
|
1661
1547
|
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
1662
1548
|
const hdr = `${theme.fg("toolTitle", theme.bold("edit"))} ${theme.fg("accent", sp(fp))}`;
|
|
1663
|
-
if (
|
|
1664
|
-
|
|
1665
|
-
|
|
1549
|
+
if (ctx.argsComplete && operations.length > 0) {
|
|
1550
|
+
const { totalAdded, totalRemoved } = summarizeEditOperations(operations);
|
|
1551
|
+
text.setText(`${hdr} ${theme.fg("muted", summarize(totalAdded, totalRemoved))}`);
|
|
1666
1552
|
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
ctx.state._pk = pk;
|
|
1670
|
-
ctx.state._pt = `${hdr} ${theme.fg("muted", "(rendering…)")}`;
|
|
1671
|
-
const lg = (0, hunk_preview_js_1.lang)(fp);
|
|
1672
|
-
const dc = (0, hunk_preview_js_1.resolveDiffColors)(theme);
|
|
1673
|
-
if (operations.length === 1) {
|
|
1674
|
-
const diff = (0, diff_js_1.parseDiff)(operations[0].oldText, operations[0].newText);
|
|
1675
|
-
(0, hunk_preview_js_1.renderSplit)(diff, lg, MAX_PREVIEW_LINES, dc, termW())
|
|
1676
|
-
.then((rendered) => {
|
|
1677
|
-
if (ctx.state._pk !== pk)
|
|
1678
|
-
return;
|
|
1679
|
-
ctx.state._pt = `${hdr}\n${summarize(diff.added, diff.removed)}\n${rendered}`;
|
|
1680
|
-
ctx.invalidate();
|
|
1681
|
-
})
|
|
1682
|
-
.catch(() => {
|
|
1683
|
-
if (ctx.state._pk !== pk)
|
|
1684
|
-
return;
|
|
1685
|
-
ctx.state._pt = `${hdr} ${summarize(diff.added, diff.removed)}`;
|
|
1686
|
-
ctx.invalidate();
|
|
1687
|
-
});
|
|
1688
|
-
}
|
|
1689
|
-
else {
|
|
1690
|
-
const { diffs, summary } = summarizeEditOperations(operations);
|
|
1691
|
-
const maxShown = Math.min(operations.length, 3);
|
|
1692
|
-
const previewLines = Math.max(8, Math.floor(MAX_PREVIEW_LINES / maxShown));
|
|
1693
|
-
Promise.all(diffs.slice(0, maxShown).map((diff, index) => (0, hunk_preview_js_1.renderSplit)(diff, lg, previewLines, dc, termW())
|
|
1694
|
-
.then((rendered) => `Edit ${index + 1}/${operations.length}\n${rendered}`)
|
|
1695
|
-
.catch(() => `Edit ${index + 1}/${operations.length} ${summarize(diff.added, diff.removed)}`)))
|
|
1696
|
-
.then((sections) => {
|
|
1697
|
-
if (ctx.state._pk !== pk)
|
|
1698
|
-
return;
|
|
1699
|
-
const remainder = operations.length - maxShown;
|
|
1700
|
-
const suffix = remainder > 0 ? `\n${theme.fg("muted", `… ${remainder} more edit blocks`)}` : "";
|
|
1701
|
-
ctx.state._pt = `${hdr}\n${operations.length} edits ${summary}\n\n${sections.join("\n\n")}${suffix}`;
|
|
1702
|
-
ctx.invalidate();
|
|
1703
|
-
})
|
|
1704
|
-
.catch(() => {
|
|
1705
|
-
if (ctx.state._pk !== pk)
|
|
1706
|
-
return;
|
|
1707
|
-
ctx.state._pt = `${hdr} ${operations.length} edits ${summary}`;
|
|
1708
|
-
ctx.invalidate();
|
|
1709
|
-
});
|
|
1710
|
-
}
|
|
1553
|
+
else {
|
|
1554
|
+
text.setText(hdr);
|
|
1711
1555
|
}
|
|
1712
|
-
text.setText(ctx.state._pt ?? hdr);
|
|
1713
1556
|
return text;
|
|
1714
1557
|
},
|
|
1715
1558
|
renderResult(result, _opt, theme, ctx) {
|
|
1716
|
-
const text = ctx.lastComponent
|
|
1559
|
+
const text = getWidthAwareText(ctx.lastComponent);
|
|
1717
1560
|
if (ctx.isError) {
|
|
1718
1561
|
const e = result.content
|
|
1719
1562
|
?.filter((c) => c.type === "text")
|
|
1720
1563
|
.map((c) => c.text || "")
|
|
1721
1564
|
.join("\n") ?? "Error";
|
|
1565
|
+
text.__piDiffTask = undefined;
|
|
1722
1566
|
text.setText(`\n${theme.fg("error", e)}`);
|
|
1723
1567
|
return text;
|
|
1724
1568
|
}
|
|
1725
|
-
|
|
1726
|
-
|
|
1569
|
+
const d = result.details;
|
|
1570
|
+
if (d?._type === "editInfo" && d.diff) {
|
|
1571
|
+
const themeKey = sharedThemeCacheKey(theme);
|
|
1572
|
+
const dc = resolveSharedDiffColors(theme);
|
|
1573
|
+
const loc = d.editLine > 0 ? ` ${theme.fg("muted", `at line ${d.editLine}`)}` : "";
|
|
1574
|
+
text.__piDiffTask = {
|
|
1575
|
+
placeholder: ` ${d.summary}${loc}\n${theme.fg("muted", " rendering diff…")}`,
|
|
1576
|
+
fallback: ` ${d.summary}${loc}`,
|
|
1577
|
+
invalidate: ctx.invalidate,
|
|
1578
|
+
key: (width) => `ed:${themeKey}:${width}:${d.summary}:${d.diff?.lines?.length ?? 0}:${d.language ?? ""}`,
|
|
1579
|
+
render: async (width) => ` ${d.summary}${loc}\n${await renderSharedSplit(d.diff, d.language, MAX_PREVIEW_LINES, dc, width)}`,
|
|
1580
|
+
};
|
|
1581
|
+
return text;
|
|
1582
|
+
}
|
|
1583
|
+
if (d?._type === "editInfo") {
|
|
1584
|
+
const { summary: s, editLine } = d;
|
|
1727
1585
|
const loc = editLine > 0 ? ` ${theme.fg("muted", `at line ${editLine}`)}` : "";
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
const pad = Math.max(0, termW() - vis);
|
|
1731
|
-
text.setText(`${content}${" ".repeat(pad)}`);
|
|
1586
|
+
text.__piDiffTask = undefined;
|
|
1587
|
+
text.setText(` ${s}${loc}`);
|
|
1732
1588
|
return text;
|
|
1733
1589
|
}
|
|
1734
|
-
if (
|
|
1735
|
-
const { summary: s, editCount, diffLineCount } =
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
const pad = Math.max(0, termW() - vis);
|
|
1739
|
-
text.setText(`${content}${" ".repeat(pad)}`);
|
|
1590
|
+
if (d?._type === "multiEditInfo") {
|
|
1591
|
+
const { summary: s, editCount, diffLineCount } = d;
|
|
1592
|
+
text.__piDiffTask = undefined;
|
|
1593
|
+
text.setText(` ${editCount} edits ${s}${typeof diffLineCount === "number" ? ` ${theme.fg("muted", `(${diffLineCount} diff lines)`)}` : ""}`);
|
|
1740
1594
|
return text;
|
|
1741
1595
|
}
|
|
1596
|
+
text.__piDiffTask = undefined;
|
|
1742
1597
|
text.setText(` ${theme.fg("dim", String(result?.content?.[0]?.text ?? "edited").slice(0, 120))}`);
|
|
1743
1598
|
return text;
|
|
1744
1599
|
},
|