@heyhuynhgiabuu/pi-diff 0.5.1 → 0.5.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/README.md +54 -50
- package/dist/core/diff.d.ts +50 -0
- package/dist/core/diff.d.ts.map +1 -1
- package/dist/core/diff.js +274 -3
- package/dist/core/diff.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +146 -113
- package/dist/index.js.map +1 -1
- package/dist/review/hunk-preview.d.ts +1 -1
- package/dist/review/hunk-preview.d.ts.map +1 -1
- package/dist/review/hunk-preview.js +30 -24
- package/dist/review/hunk-preview.js.map +1 -1
- package/dist/review/interactive.d.ts.map +1 -1
- package/dist/review/interactive.js +1 -5
- package/dist/review/interactive.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24,12 +24,10 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
24
24
|
import { extname, relative } from "node:path";
|
|
25
25
|
import { codeToANSI } from "@shikijs/cli";
|
|
26
26
|
import * as Diff from "diff";
|
|
27
|
-
import { parseDiff } from "./core/diff.js";
|
|
27
|
+
import { computeHunkBlocks, getSepStyle, parseDiff, parsePatchFiles, resolveSepStyle, sepLabelSplit, sepLabelUnified } from "./core/diff.js";
|
|
28
|
+
import { resolveLinesFromPatch } from "./core/resolve-lines.js";
|
|
28
29
|
import { registerReviewDiffCommand } from "./review/command.js";
|
|
29
|
-
import { formatReviewMarkdown } from "./review/export.js";
|
|
30
|
-
import { countReviewDiffLines, readGitDiff } from "./review/git.js";
|
|
31
30
|
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";
|
|
33
31
|
const DIFF_PRESETS = {
|
|
34
32
|
default: {
|
|
35
33
|
name: "default",
|
|
@@ -926,10 +924,9 @@ async function renderUnified(diff, language, max = MAX_RENDER_LINES, dc = DEFAUL
|
|
|
926
924
|
}
|
|
927
925
|
while (idx < vis.length) {
|
|
928
926
|
const l = vis[idx];
|
|
929
|
-
// Hunk separator — collapsed context
|
|
927
|
+
// Hunk separator — collapsed context with optional function context
|
|
930
928
|
if (l.type === "sep") {
|
|
931
|
-
const
|
|
932
|
-
const label = gap && gap > 0 ? ` ${gap} unmodified lines ` : "···";
|
|
929
|
+
const label = sepLabelUnified(getSepStyle(), l.hunkMeta, l.newNum, l.content);
|
|
933
930
|
const totalW = Math.min(tw, 72);
|
|
934
931
|
const pad = Math.max(0, totalW - label.length - 2);
|
|
935
932
|
const half1 = Math.floor(pad / 2), half2 = pad - half1;
|
|
@@ -1009,16 +1006,23 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1009
1006
|
let i = 0;
|
|
1010
1007
|
while (i < diff.lines.length) {
|
|
1011
1008
|
const l = diff.lines[i];
|
|
1012
|
-
if (l.type === "
|
|
1009
|
+
if (l.type === "ctx") {
|
|
1010
|
+
rows.push({ left: l, right: l });
|
|
1011
|
+
i++;
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
if (l.type === "sep") {
|
|
1013
1015
|
rows.push({ left: l, right: l });
|
|
1014
1016
|
i++;
|
|
1015
1017
|
continue;
|
|
1016
1018
|
}
|
|
1017
|
-
|
|
1019
|
+
// Collect del/add block
|
|
1020
|
+
const dels = [];
|
|
1018
1021
|
while (i < diff.lines.length && diff.lines[i].type === "del") {
|
|
1019
1022
|
dels.push(diff.lines[i]);
|
|
1020
1023
|
i++;
|
|
1021
1024
|
}
|
|
1025
|
+
const adds = [];
|
|
1022
1026
|
while (i < diff.lines.length && diff.lines[i].type === "add") {
|
|
1023
1027
|
adds.push(diff.lines[i]);
|
|
1024
1028
|
i++;
|
|
@@ -1054,10 +1058,9 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1054
1058
|
const g = ` ${gPat}${FG_RULE}│${RST} `;
|
|
1055
1059
|
return { gutter: g, contGutter: g, bodyRows: [stripes(cw, stripeRow)] };
|
|
1056
1060
|
}
|
|
1057
|
-
// Hunk separator
|
|
1061
|
+
// Hunk separator with optional function context
|
|
1058
1062
|
if (line.type === "sep") {
|
|
1059
|
-
const
|
|
1060
|
-
const label = gap && gap > 0 ? `··· ${gap} lines ···` : "···";
|
|
1063
|
+
const label = sepLabelSplit(getSepStyle(), line.hunkMeta, line.newNum, line.content);
|
|
1061
1064
|
const g = `${BG_BASE} ${FG_DIM}${fit("", nw + 2)}${RST}${FG_RULE}│${RST} `;
|
|
1062
1065
|
return { gutter: g, contGutter: g, bodyRows: [`${BG_BASE}${FG_DIM}${fit(label, cw)}${RST}`] };
|
|
1063
1066
|
}
|
|
@@ -1139,35 +1142,20 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1139
1142
|
// Extension
|
|
1140
1143
|
// ---------------------------------------------------------------------------
|
|
1141
1144
|
export const __testing = {
|
|
1145
|
+
computeHunkBlocks,
|
|
1142
1146
|
normalizeShikiContrast,
|
|
1147
|
+
getSepStyle,
|
|
1143
1148
|
parseDiff,
|
|
1149
|
+
parsePatchFiles,
|
|
1150
|
+
resolveSepStyle,
|
|
1144
1151
|
renderSplit,
|
|
1145
1152
|
renderUnified,
|
|
1146
1153
|
};
|
|
1147
|
-
function reviewGitDiffMode(params) {
|
|
1148
|
-
const base = typeof params.base === "string" ? params.base.trim() : "";
|
|
1149
|
-
return base ? { type: "branch", base } : { type: "working-tree" };
|
|
1150
|
-
}
|
|
1151
|
-
function reviewGitDiffMaxLines(params) {
|
|
1152
|
-
if (params.maxLinesPerHunk === undefined)
|
|
1153
|
-
return undefined;
|
|
1154
|
-
const maxLines = Number(params.maxLinesPerHunk);
|
|
1155
|
-
if (!Number.isInteger(maxLines) || maxLines < 1) {
|
|
1156
|
-
throw new Error("maxLinesPerHunk must be a positive integer");
|
|
1157
|
-
}
|
|
1158
|
-
return maxLines;
|
|
1159
|
-
}
|
|
1160
|
-
function normalizeOptionalPositiveInteger(value, name) {
|
|
1161
|
-
if (value === undefined || value === null)
|
|
1162
|
-
return undefined;
|
|
1163
|
-
const number = Number(value);
|
|
1164
|
-
if (!Number.isInteger(number) || number < 1)
|
|
1165
|
-
throw new Error(`${name} must be a positive integer`);
|
|
1166
|
-
return number;
|
|
1167
|
-
}
|
|
1168
1154
|
export default async function diffRendererExtension(pi) {
|
|
1169
1155
|
// Apply diff theme palette from settings/presets before rendering
|
|
1170
1156
|
applySharedDiffPalette();
|
|
1157
|
+
// Resolve hunk separator style from env var
|
|
1158
|
+
resolveSepStyle();
|
|
1171
1159
|
let createWriteTool, createEditTool, getMarkdownTheme, TextComponent, MarkdownComponent;
|
|
1172
1160
|
try {
|
|
1173
1161
|
const sdk = await import("@earendil-works/pi-coding-agent");
|
|
@@ -1226,121 +1214,127 @@ export default async function diffRendererExtension(pi) {
|
|
|
1226
1214
|
return text;
|
|
1227
1215
|
}
|
|
1228
1216
|
registerReviewDiffCommand(pi, cwd);
|
|
1217
|
+
// ── resolve_lines tool ────────────────────────────────────────────────
|
|
1229
1218
|
pi.registerTool({
|
|
1230
|
-
name: "
|
|
1231
|
-
label: "
|
|
1232
|
-
description: `
|
|
1219
|
+
name: "resolve_lines",
|
|
1220
|
+
label: "Resolve Lines",
|
|
1221
|
+
description: `Resolve line numbers for an LLM code-review comment by matching its existing_code snippet against the git diff hunks. Uses a three-tier algorithm: new-side hunk match, old-side hunk match, then full file-content scan. Returns \`startLine\` and \`endLine\` when found, or \`unresolved: true\` when the snippet cannot be located.
|
|
1233
1222
|
|
|
1234
|
-
|
|
1223
|
+
Three-tier resolution:
|
|
1224
|
+
1. Match against diff hunks (new-file side) — context + added lines
|
|
1225
|
+
2. Match against diff hunks (old-file side) — context + deleted lines
|
|
1226
|
+
3. Scan full file content for a consecutive line match
|
|
1227
|
+
|
|
1228
|
+
Use after receiving a review comment with \`existing_code\` but zero or drifted line numbers. The tool fetches the current git diff for the file automatically when \`patchText\` is omitted.
|
|
1235
1229
|
|
|
1236
1230
|
Examples:
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
review_git_diff({ file: "src/index.ts", hunkId: "src/index.ts:10:12" })`,
|
|
1241
|
-
promptSnippet: "Open a read-only Git review markdown panel for local changes. Prefer /review-diff for the interactive TUI workflow.",
|
|
1231
|
+
resolve_lines({ existingCode: "const x = 1;", filePath: "src/index.ts" })
|
|
1232
|
+
resolve_lines({ existingCode: "function foo() {", filePath: "src/utils.ts", patchText: "@@ -1,3 +1,4 @@..." })`,
|
|
1233
|
+
promptSnippet: "Resolve line numbers for an LLM review comment by matching existing_code against git diff hunks.",
|
|
1242
1234
|
parameters: {
|
|
1243
1235
|
type: "object",
|
|
1244
1236
|
properties: {
|
|
1245
|
-
|
|
1237
|
+
existingCode: {
|
|
1246
1238
|
type: "string",
|
|
1247
|
-
description: "
|
|
1239
|
+
description: "The code snippet from the LLM review comment that shows the problematic code. Required.",
|
|
1248
1240
|
},
|
|
1249
|
-
|
|
1241
|
+
filePath: {
|
|
1250
1242
|
type: "string",
|
|
1251
|
-
description: "
|
|
1243
|
+
description: "Path to the file this comment is about, relative to repo root. Required.",
|
|
1252
1244
|
},
|
|
1253
|
-
|
|
1245
|
+
patchText: {
|
|
1254
1246
|
type: "string",
|
|
1255
|
-
description: "Optional
|
|
1256
|
-
},
|
|
1257
|
-
includeRawDiff: {
|
|
1258
|
-
type: "boolean",
|
|
1259
|
-
description: "Return legacy raw review markdown instead of the interactive panel when true.",
|
|
1260
|
-
},
|
|
1261
|
-
raw: {
|
|
1262
|
-
type: "boolean",
|
|
1263
|
-
description: "Alias for includeRawDiff.",
|
|
1264
|
-
},
|
|
1265
|
-
maxLinesPerHunk: {
|
|
1266
|
-
type: "number",
|
|
1267
|
-
description: "Optional positive integer limit for lines shown per hunk.",
|
|
1247
|
+
description: "Optional raw unified diff text for the file. When omitted, pi-diff fetches the git diff automatically.",
|
|
1268
1248
|
},
|
|
1269
|
-
|
|
1270
|
-
type: "
|
|
1271
|
-
description: "Optional
|
|
1272
|
-
},
|
|
1273
|
-
maxHunks: {
|
|
1274
|
-
type: "number",
|
|
1275
|
-
description: "Optional positive integer limit for hunks shown in the focused file view.",
|
|
1249
|
+
fileContent: {
|
|
1250
|
+
type: "string",
|
|
1251
|
+
description: "Optional full new-file content for fallback line scan after hunk matching fails.",
|
|
1276
1252
|
},
|
|
1277
1253
|
},
|
|
1254
|
+
required: ["existingCode", "filePath"],
|
|
1278
1255
|
additionalProperties: false,
|
|
1279
1256
|
},
|
|
1280
1257
|
async execute(_tid, params = {}) {
|
|
1281
1258
|
try {
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1259
|
+
const { existingCode, filePath, patchText, fileContent } = params ?? {};
|
|
1260
|
+
if (!existingCode || !filePath) {
|
|
1261
|
+
return { content: [{ type: "text", text: "Error: existingCode and filePath are required" }] };
|
|
1262
|
+
}
|
|
1263
|
+
// Fetch git diff if patchText not provided
|
|
1264
|
+
let patch = patchText;
|
|
1265
|
+
if (!patch) {
|
|
1266
|
+
const { execFileSync } = await import("node:child_process");
|
|
1267
|
+
try {
|
|
1268
|
+
patch = execFileSync("git", ["diff", "--no-ext-diff", "HEAD", "--", filePath], {
|
|
1269
|
+
cwd,
|
|
1270
|
+
encoding: "utf8",
|
|
1271
|
+
maxBuffer: 1024 * 1024,
|
|
1272
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1273
|
+
}).trim();
|
|
1274
|
+
// Also try unstaged diff
|
|
1275
|
+
const unstaged = execFileSync("git", ["diff", "--no-ext-diff", "--", filePath], {
|
|
1276
|
+
cwd,
|
|
1277
|
+
encoding: "utf8",
|
|
1278
|
+
maxBuffer: 1024 * 1024,
|
|
1279
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1280
|
+
}).trim();
|
|
1281
|
+
if (unstaged) {
|
|
1282
|
+
patch = patch ? [patch, unstaged].filter(Boolean).join("\n") : unstaged;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
catch {
|
|
1286
|
+
return {
|
|
1287
|
+
content: [{ type: "text", text: `Error: could not read git diff for ${filePath}` }],
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
if (!patch) {
|
|
1292
|
+
return {
|
|
1293
|
+
content: [{ type: "text", text: `No diff found for ${filePath} — file may not have changes` }],
|
|
1294
|
+
details: { unresolved: true },
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
const result = resolveLinesFromPatch(existingCode, patch, fileContent);
|
|
1298
|
+
if ("unresolved" in result) {
|
|
1299
|
+
return {
|
|
1300
|
+
content: [{ type: "text", text: `Could not resolve line numbers for existing_code in ${filePath}` }],
|
|
1301
|
+
details: { unresolved: true },
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1295
1304
|
return {
|
|
1296
|
-
content: [{ type: "text", text:
|
|
1297
|
-
details: {
|
|
1298
|
-
_type: "reviewGitDiff",
|
|
1299
|
-
markdown,
|
|
1300
|
-
mode: String(diff.mode),
|
|
1301
|
-
fileCount: diff.files.length,
|
|
1302
|
-
insertions: counts.insertions,
|
|
1303
|
-
deletions: counts.deletions,
|
|
1304
|
-
commentCount: 0,
|
|
1305
|
-
focusedFile: safeParams.file,
|
|
1306
|
-
focusedHunk: safeParams.hunkId,
|
|
1307
|
-
error: undefined,
|
|
1308
|
-
},
|
|
1305
|
+
content: [{ type: "text", text: `Resolved ${filePath}:${result.startLine}-${result.endLine}` }],
|
|
1306
|
+
details: { startLine: result.startLine, endLine: result.endLine },
|
|
1309
1307
|
};
|
|
1310
1308
|
}
|
|
1311
1309
|
catch (error) {
|
|
1312
1310
|
const message = error instanceof Error ? error.message : String(error);
|
|
1313
1311
|
return {
|
|
1314
1312
|
content: [{ type: "text", text: `Error: ${message}` }],
|
|
1315
|
-
details: {
|
|
1313
|
+
details: { unresolved: true, error: message },
|
|
1316
1314
|
};
|
|
1317
1315
|
}
|
|
1318
1316
|
},
|
|
1319
1317
|
renderCall(args, theme, ctx) {
|
|
1320
1318
|
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
1321
|
-
const
|
|
1322
|
-
|
|
1323
|
-
const focus = typeof safeArgs?.file === "string" && safeArgs.file.trim() ? ` • ${safeArgs.file.trim()}` : "";
|
|
1324
|
-
text.setText(`${theme.fg("toolTitle", theme.bold("review_git_diff"))}${theme.fg("muted", `${base}${focus}`)}`);
|
|
1319
|
+
const fp = typeof args?.filePath === "string" ? args.filePath : "";
|
|
1320
|
+
text.setText(`${theme.fg("toolTitle", theme.bold("resolve_lines"))} ${theme.fg("accent", fp)}`);
|
|
1325
1321
|
return text;
|
|
1326
1322
|
},
|
|
1327
1323
|
renderResult(result, _opt, theme, ctx) {
|
|
1328
1324
|
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
1329
1325
|
if (ctx.isError || result.details?.error) {
|
|
1330
|
-
text.setText(`\n${theme.fg("error", result.details?.error ?? "
|
|
1326
|
+
text.setText(`\n${theme.fg("error", result.details?.error ?? "resolve_lines failed")}`);
|
|
1331
1327
|
return text;
|
|
1332
1328
|
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
const comments = details.commentCount ? ` • ${details.commentCount} comments` : "";
|
|
1340
|
-
text.setText(` ${summary}${theme.fg("muted", comments)}\n${theme.fg("muted", " Interactive review panel generated in the tool result.")}`);
|
|
1329
|
+
if (result.details?.unresolved) {
|
|
1330
|
+
text.setText(` ${theme.fg("muted", "unresolved")}`);
|
|
1331
|
+
return text;
|
|
1332
|
+
}
|
|
1333
|
+
if (result.details?.startLine != null) {
|
|
1334
|
+
text.setText(` ${theme.fg("success", `lines ${result.details.startLine}-${result.details.endLine}`)}`);
|
|
1341
1335
|
return text;
|
|
1342
1336
|
}
|
|
1343
|
-
text.setText(` ${theme.fg("
|
|
1337
|
+
text.setText(` ${theme.fg("dim", String(result?.content?.[0]?.text ?? "").slice(0, 120))}`);
|
|
1344
1338
|
return text;
|
|
1345
1339
|
},
|
|
1346
1340
|
});
|
|
@@ -1365,9 +1359,10 @@ Examples:
|
|
|
1365
1359
|
const content = params.content ?? "";
|
|
1366
1360
|
// Store in details — the only custom field TUI preserves in renderResult
|
|
1367
1361
|
if (old !== null && old !== content) {
|
|
1368
|
-
const
|
|
1362
|
+
const useFull = !!params._expandGaps;
|
|
1363
|
+
const diff = parseDiff(old, content, useFull ? undefined : 3);
|
|
1369
1364
|
const lg = detectDiffLanguage(fp);
|
|
1370
|
-
result.details = { _type: "diff", summary: summarize(diff.added, diff.removed), diff, language: lg };
|
|
1365
|
+
result.details = { _type: "diff", summary: summarize(diff.added, diff.removed), diff, language: lg, oldContent: old, newContent: content };
|
|
1371
1366
|
}
|
|
1372
1367
|
else if (old === null) {
|
|
1373
1368
|
const lineCount = content ? content.split("\n").length : 0;
|
|
@@ -1530,14 +1525,39 @@ Examples:
|
|
|
1530
1525
|
catch {
|
|
1531
1526
|
editLine = 0;
|
|
1532
1527
|
}
|
|
1533
|
-
|
|
1528
|
+
const useFull = !!params._expandGaps;
|
|
1529
|
+
const diffData = useFull
|
|
1530
|
+
? parseDiff(operations[0].oldText, operations[0].newText, undefined)
|
|
1531
|
+
: diffs[0];
|
|
1532
|
+
result.details = {
|
|
1533
|
+
_type: "editInfo",
|
|
1534
|
+
summary,
|
|
1535
|
+
editLine,
|
|
1536
|
+
diff: diffData,
|
|
1537
|
+
language: lg,
|
|
1538
|
+
oldContent: operations[0].oldText,
|
|
1539
|
+
newContent: operations[0].newText,
|
|
1540
|
+
};
|
|
1534
1541
|
return result;
|
|
1535
1542
|
}
|
|
1543
|
+
// Merge all diffs into one combined view for rendering
|
|
1544
|
+
const merged = {
|
|
1545
|
+
lines: diffs.flatMap((diff, i) => [
|
|
1546
|
+
// Add separator between multiple edits
|
|
1547
|
+
...(i > 0 ? [{ type: "sep", oldNum: null, newNum: null, content: `───── Edit ${i + 1} ─────` }] : []),
|
|
1548
|
+
...diff.lines,
|
|
1549
|
+
]),
|
|
1550
|
+
added: diffs.reduce((sum, diff) => sum + diff.added, 0),
|
|
1551
|
+
removed: diffs.reduce((sum, diff) => sum + diff.removed, 0),
|
|
1552
|
+
chars: diffs.reduce((sum, diff) => sum + diff.chars, 0),
|
|
1553
|
+
};
|
|
1536
1554
|
result.details = {
|
|
1537
1555
|
_type: "multiEditInfo",
|
|
1538
1556
|
summary,
|
|
1539
1557
|
editCount: operations.length,
|
|
1540
|
-
diffLineCount:
|
|
1558
|
+
diffLineCount: merged.lines.length,
|
|
1559
|
+
diff: merged,
|
|
1560
|
+
language: lg,
|
|
1541
1561
|
};
|
|
1542
1562
|
return result;
|
|
1543
1563
|
},
|
|
@@ -1588,7 +1608,20 @@ Examples:
|
|
|
1588
1608
|
return text;
|
|
1589
1609
|
}
|
|
1590
1610
|
if (d?._type === "multiEditInfo") {
|
|
1591
|
-
const { summary: s, editCount, diffLineCount } = d;
|
|
1611
|
+
const { summary: s, editCount, diffLineCount, diff, language } = d;
|
|
1612
|
+
if (diff) {
|
|
1613
|
+
const themeKey = sharedThemeCacheKey(theme);
|
|
1614
|
+
const dc = resolveSharedDiffColors(theme);
|
|
1615
|
+
text.__piDiffTask = {
|
|
1616
|
+
placeholder: ` ${editCount} edits ${s}
|
|
1617
|
+
${theme.fg("muted", " rendering diff…")}`,
|
|
1618
|
+
fallback: ` ${editCount} edits ${s}${typeof diffLineCount === "number" ? ` ${theme.fg("muted", `(${diffLineCount} diff lines)`)}` : ""}`,
|
|
1619
|
+
invalidate: ctx.invalidate,
|
|
1620
|
+
key: (width) => `me:${themeKey}:${width}:${s}:${diff?.lines?.length ?? 0}:${language ?? ""}`,
|
|
1621
|
+
render: async (width) => ` ${editCount} edits ${s}${typeof diffLineCount === "number" ? ` ${theme.fg("muted", `(${diffLineCount} diff lines)`)}` : ""}\n${await renderSharedSplit(diff, language, MAX_PREVIEW_LINES, dc, width)}`,
|
|
1622
|
+
};
|
|
1623
|
+
return text;
|
|
1624
|
+
}
|
|
1592
1625
|
text.__piDiffTask = undefined;
|
|
1593
1626
|
text.setText(` ${editCount} edits ${s}${typeof diffLineCount === "number" ? ` ${theme.fg("muted", `(${diffLineCount} diff lines)`)}` : ""}`);
|
|
1594
1627
|
return text;
|