@heyhuynhgiabuu/pi-diff 0.4.1 → 0.5.0
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.map +1 -1
- package/dist/index.js +47 -192
- 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,7 @@ 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
|
-
(0, command_js_1.registerReviewDiffCommand)(pi, cwd);
|
|
1190
|
+
registerReviewDiffCommand(pi, cwd);
|
|
1229
1191
|
pi.registerTool({
|
|
1230
1192
|
name: "review_git_diff",
|
|
1231
1193
|
label: "Review Git Diff",
|
|
@@ -1280,18 +1242,18 @@ Examples:
|
|
|
1280
1242
|
async execute(_tid, params = {}) {
|
|
1281
1243
|
try {
|
|
1282
1244
|
const safeParams = params ?? {};
|
|
1283
|
-
const diff = await
|
|
1245
|
+
const diff = await readGitDiff(cwd, reviewGitDiffMode(safeParams));
|
|
1284
1246
|
const maxLinesPerHunk = reviewGitDiffMaxLines(safeParams);
|
|
1285
1247
|
const markdown = safeParams.includeRawDiff || safeParams.raw
|
|
1286
|
-
?
|
|
1287
|
-
:
|
|
1248
|
+
? formatReviewMarkdown(diff, { includeRawDiff: true, maxLinesPerHunk })
|
|
1249
|
+
: formatInteractiveReviewPanel(diff, [], {
|
|
1288
1250
|
file: safeParams.file,
|
|
1289
1251
|
hunkId: safeParams.hunkId,
|
|
1290
1252
|
maxFiles: normalizeOptionalPositiveInteger(safeParams.maxFiles, "maxFiles"),
|
|
1291
1253
|
maxHunks: normalizeOptionalPositiveInteger(safeParams.maxHunks, "maxHunks"),
|
|
1292
1254
|
maxLinesPerHunk,
|
|
1293
1255
|
});
|
|
1294
|
-
const counts =
|
|
1256
|
+
const counts = countReviewDiffLines(diff);
|
|
1295
1257
|
return {
|
|
1296
1258
|
content: [{ type: "text", text: markdown }],
|
|
1297
1259
|
details: {
|
|
@@ -1301,7 +1263,7 @@ Examples:
|
|
|
1301
1263
|
fileCount: diff.files.length,
|
|
1302
1264
|
insertions: counts.insertions,
|
|
1303
1265
|
deletions: counts.deletions,
|
|
1304
|
-
commentCount:
|
|
1266
|
+
commentCount: 0,
|
|
1305
1267
|
focusedFile: safeParams.file,
|
|
1306
1268
|
focusedHunk: safeParams.hunkId,
|
|
1307
1269
|
},
|
|
@@ -1342,113 +1304,6 @@ Examples:
|
|
|
1342
1304
|
return text;
|
|
1343
1305
|
},
|
|
1344
1306
|
});
|
|
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
1307
|
// =======================================================================
|
|
1453
1308
|
// write
|
|
1454
1309
|
// =======================================================================
|
|
@@ -1460,8 +1315,8 @@ Examples:
|
|
|
1460
1315
|
const fp = params.path ?? params.file_path ?? "";
|
|
1461
1316
|
let old = null;
|
|
1462
1317
|
try {
|
|
1463
|
-
if (fp &&
|
|
1464
|
-
old =
|
|
1318
|
+
if (fp && existsSync(fp))
|
|
1319
|
+
old = readFileSync(fp, "utf-8");
|
|
1465
1320
|
}
|
|
1466
1321
|
catch {
|
|
1467
1322
|
old = null;
|
|
@@ -1470,8 +1325,8 @@ Examples:
|
|
|
1470
1325
|
const content = params.content ?? "";
|
|
1471
1326
|
// Store in details — the only custom field TUI preserves in renderResult
|
|
1472
1327
|
if (old !== null && old !== content) {
|
|
1473
|
-
const diff =
|
|
1474
|
-
const lg = (
|
|
1328
|
+
const diff = parseDiff(old, content);
|
|
1329
|
+
const lg = detectDiffLanguage(fp);
|
|
1475
1330
|
result.details = { _type: "diff", summary: summarize(diff.added, diff.removed), diff, language: lg };
|
|
1476
1331
|
}
|
|
1477
1332
|
else if (old === null) {
|
|
@@ -1485,7 +1340,7 @@ Examples:
|
|
|
1485
1340
|
},
|
|
1486
1341
|
renderCall(args, theme, ctx) {
|
|
1487
1342
|
const fp = args?.path ?? args?.file_path ?? "";
|
|
1488
|
-
const isNew = !fp || !
|
|
1343
|
+
const isNew = !fp || !existsSync(fp);
|
|
1489
1344
|
const label = isNew ? "create" : "write";
|
|
1490
1345
|
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
1491
1346
|
const hdr = `${theme.fg("toolTitle", theme.bold(label))} ${theme.fg("accent", sp(fp))}`;
|
|
@@ -1497,11 +1352,11 @@ Examples:
|
|
|
1497
1352
|
}
|
|
1498
1353
|
// New file preview with Shiki
|
|
1499
1354
|
if (args?.content && ctx.argsComplete && isNew) {
|
|
1500
|
-
const previewKey = `create:${(
|
|
1355
|
+
const previewKey = `create:${sharedThemeCacheKey(theme)}:${fp}:${String(args.content).length}`;
|
|
1501
1356
|
if (ctx.state._previewKey !== previewKey) {
|
|
1502
1357
|
ctx.state._previewKey = previewKey;
|
|
1503
1358
|
ctx.state._previewText = hdr;
|
|
1504
|
-
const lg = (
|
|
1359
|
+
const lg = detectDiffLanguage(fp);
|
|
1505
1360
|
hlBlock(args.content, lg)
|
|
1506
1361
|
.then((lines) => {
|
|
1507
1362
|
if (ctx.state._previewKey !== previewKey)
|
|
@@ -1536,12 +1391,12 @@ Examples:
|
|
|
1536
1391
|
const d = result.details;
|
|
1537
1392
|
if (d?._type === "diff") {
|
|
1538
1393
|
const w = termW();
|
|
1539
|
-
const key = `wd:${(
|
|
1394
|
+
const key = `wd:${sharedThemeCacheKey(theme)}:${w}:${d.summary}:${d.diff?.lines?.length ?? 0}:${d.language ?? ""}`;
|
|
1540
1395
|
if (ctx.state._wdk !== key) {
|
|
1541
1396
|
ctx.state._wdk = key;
|
|
1542
1397
|
ctx.state._wdt = ` ${d.summary}\n${theme.fg("muted", " rendering diff…")}`;
|
|
1543
|
-
const dc = (
|
|
1544
|
-
(
|
|
1398
|
+
const dc = resolveSharedDiffColors(theme);
|
|
1399
|
+
renderSharedSplit(d.diff, d.language, MAX_RENDER_LINES, dc, w)
|
|
1545
1400
|
.then((rendered) => {
|
|
1546
1401
|
if (ctx.state._wdk !== key)
|
|
1547
1402
|
return;
|
|
@@ -1564,11 +1419,11 @@ Examples:
|
|
|
1564
1419
|
}
|
|
1565
1420
|
if (d?._type === "new") {
|
|
1566
1421
|
const { lines: lineCount, content: rawContent, filePath: fp } = d;
|
|
1567
|
-
const pk = `nf:${(
|
|
1422
|
+
const pk = `nf:${sharedThemeCacheKey(theme)}:${fp}:${lineCount}`;
|
|
1568
1423
|
if (ctx.state._nfk !== pk) {
|
|
1569
1424
|
ctx.state._nfk = pk;
|
|
1570
1425
|
ctx.state._nft = ` ${theme.fg("success", `✓ new file (${lineCount} lines)`)}`;
|
|
1571
|
-
const lg = (
|
|
1426
|
+
const lg = detectDiffLanguage(fp);
|
|
1572
1427
|
if (rawContent) {
|
|
1573
1428
|
hlBlock(rawContent, lg)
|
|
1574
1429
|
.then((hlLines) => {
|
|
@@ -1611,7 +1466,7 @@ Examples:
|
|
|
1611
1466
|
return oldText && oldText !== newText ? [{ oldText, newText }] : [];
|
|
1612
1467
|
}
|
|
1613
1468
|
function summarizeEditOperations(operations) {
|
|
1614
|
-
const diffs = operations.map((edit) =>
|
|
1469
|
+
const diffs = operations.map((edit) => parseDiff(edit.oldText, edit.newText));
|
|
1615
1470
|
const totalAdded = diffs.reduce((sum, diff) => sum + diff.added, 0);
|
|
1616
1471
|
const totalRemoved = diffs.reduce((sum, diff) => sum + diff.removed, 0);
|
|
1617
1472
|
return {
|
|
@@ -1634,8 +1489,8 @@ Examples:
|
|
|
1634
1489
|
if (operations.length === 1) {
|
|
1635
1490
|
let editLine = 0;
|
|
1636
1491
|
try {
|
|
1637
|
-
if (fp &&
|
|
1638
|
-
const f =
|
|
1492
|
+
if (fp && existsSync(fp)) {
|
|
1493
|
+
const f = readFileSync(fp, "utf-8");
|
|
1639
1494
|
const idx = f.indexOf(operations[0].newText);
|
|
1640
1495
|
if (idx >= 0)
|
|
1641
1496
|
editLine = f.slice(0, idx).split("\n").length;
|
|
@@ -1664,15 +1519,15 @@ Examples:
|
|
|
1664
1519
|
text.setText(hdr);
|
|
1665
1520
|
return text;
|
|
1666
1521
|
}
|
|
1667
|
-
const pk = JSON.stringify({ fp, operations, theme: (
|
|
1522
|
+
const pk = JSON.stringify({ fp, operations, theme: sharedThemeCacheKey(theme), w: termW() });
|
|
1668
1523
|
if (ctx.state._pk !== pk) {
|
|
1669
1524
|
ctx.state._pk = pk;
|
|
1670
1525
|
ctx.state._pt = `${hdr} ${theme.fg("muted", "(rendering…)")}`;
|
|
1671
|
-
const lg = (
|
|
1672
|
-
const dc = (
|
|
1526
|
+
const lg = detectDiffLanguage(fp);
|
|
1527
|
+
const dc = resolveSharedDiffColors(theme);
|
|
1673
1528
|
if (operations.length === 1) {
|
|
1674
|
-
const diff =
|
|
1675
|
-
(
|
|
1529
|
+
const diff = parseDiff(operations[0].oldText, operations[0].newText);
|
|
1530
|
+
renderSharedSplit(diff, lg, MAX_PREVIEW_LINES, dc, termW())
|
|
1676
1531
|
.then((rendered) => {
|
|
1677
1532
|
if (ctx.state._pk !== pk)
|
|
1678
1533
|
return;
|
|
@@ -1690,7 +1545,7 @@ Examples:
|
|
|
1690
1545
|
const { diffs, summary } = summarizeEditOperations(operations);
|
|
1691
1546
|
const maxShown = Math.min(operations.length, 3);
|
|
1692
1547
|
const previewLines = Math.max(8, Math.floor(MAX_PREVIEW_LINES / maxShown));
|
|
1693
|
-
Promise.all(diffs.slice(0, maxShown).map((diff, index) => (
|
|
1548
|
+
Promise.all(diffs.slice(0, maxShown).map((diff, index) => renderSharedSplit(diff, lg, previewLines, dc, termW())
|
|
1694
1549
|
.then((rendered) => `Edit ${index + 1}/${operations.length}\n${rendered}`)
|
|
1695
1550
|
.catch(() => `Edit ${index + 1}/${operations.length} ${summarize(diff.added, diff.removed)}`)))
|
|
1696
1551
|
.then((sections) => {
|