@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/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
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
25
- if (k2 === undefined) k2 = k;
26
- var desc = Object.getOwnPropertyDescriptor(m, k);
27
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
28
- desc = { enumerable: true, get: function() { return m[k]; } };
29
- }
30
- Object.defineProperty(o, k2, desc);
31
- }) : (function(o, m, k, k2) {
32
- if (k2 === undefined) k2 = k;
33
- o[k2] = m[k];
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 ((0, node_fs_1.existsSync)(p)) {
249
- const raw = JSON.parse((0, node_fs_1.readFileSync)(p, "utf-8"));
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 = (0, node_path_1.relative)(cwd, p);
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[(0, node_path_1.extname)(fp).slice(1).toLowerCase()];
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
- (0, cli_1.codeToANSI)("", "typescript", THEME).catch(() => { });
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 (0, cli_1.codeToANSI)(code, language, THEME));
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
- exports.__testing = {
1141
+ export const __testing = {
1179
1142
  normalizeShikiContrast,
1180
- parseDiff: diff_js_1.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
- (0, hunk_preview_js_1.applyDiffPalette)();
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
- const reviewComments = [];
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 (0, git_js_1.readGitDiff)(cwd, reviewGitDiffMode(safeParams));
1245
+ const diff = await readGitDiff(cwd, reviewGitDiffMode(safeParams));
1284
1246
  const maxLinesPerHunk = reviewGitDiffMaxLines(safeParams);
1285
1247
  const markdown = safeParams.includeRawDiff || safeParams.raw
1286
- ? (0, export_js_1.formatReviewMarkdown)(diff, { includeRawDiff: true, maxLinesPerHunk })
1287
- : (0, interactive_js_1.formatInteractiveReviewPanel)(diff, reviewComments, {
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 = (0, git_js_1.countReviewDiffLines)(diff);
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: reviewComments.length,
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 && (0, node_fs_1.existsSync)(fp))
1464
- old = (0, node_fs_1.readFileSync)(fp, "utf-8");
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 = (0, diff_js_1.parseDiff)(old, content);
1474
- const lg = (0, hunk_preview_js_1.lang)(fp);
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 || !(0, node_fs_1.existsSync)(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:${(0, hunk_preview_js_1.themeCacheKey)(theme)}:${fp}:${String(args.content).length}`;
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 = (0, hunk_preview_js_1.lang)(fp);
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:${(0, hunk_preview_js_1.themeCacheKey)(theme)}:${w}:${d.summary}:${d.diff?.lines?.length ?? 0}:${d.language ?? ""}`;
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 = (0, hunk_preview_js_1.resolveDiffColors)(theme);
1544
- (0, hunk_preview_js_1.renderSplit)(d.diff, d.language, MAX_RENDER_LINES, dc, w)
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:${(0, hunk_preview_js_1.themeCacheKey)(theme)}:${fp}:${lineCount}`;
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 = (0, hunk_preview_js_1.lang)(fp);
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) => (0, diff_js_1.parseDiff)(edit.oldText, edit.newText));
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 && (0, node_fs_1.existsSync)(fp)) {
1638
- const f = (0, node_fs_1.readFileSync)(fp, "utf-8");
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: (0, hunk_preview_js_1.themeCacheKey)(theme), w: termW() });
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 = (0, hunk_preview_js_1.lang)(fp);
1672
- const dc = (0, hunk_preview_js_1.resolveDiffColors)(theme);
1526
+ const lg = detectDiffLanguage(fp);
1527
+ const dc = resolveSharedDiffColors(theme);
1673
1528
  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())
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) => (0, hunk_preview_js_1.renderSplit)(diff, lg, previewLines, dc, termW())
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) => {