@heyhuynhgiabuu/pi-diff 0.5.5 → 0.6.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/dist/core/replace.d.ts +47 -0
- package/dist/core/replace.d.ts.map +1 -0
- package/dist/core/replace.js +496 -0
- package/dist/core/replace.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +314 -115
- package/dist/index.js.map +1 -1
- package/dist/review/file-preview.d.ts.map +1 -1
- package/dist/review/file-preview.js.map +1 -1
- package/dist/review/hunk-preview.d.ts +2 -1
- package/dist/review/hunk-preview.d.ts.map +1 -1
- package/dist/review/hunk-preview.js +68 -60
- package/dist/review/hunk-preview.js.map +1 -1
- package/package.json +5 -1
package/dist/index.js
CHANGED
|
@@ -20,12 +20,13 @@
|
|
|
20
20
|
* • Large-diff fallback (skip highlighting, still show diff)
|
|
21
21
|
* • Async rendering with invalidate() for non-blocking preview
|
|
22
22
|
*/
|
|
23
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
23
|
+
import { existsSync, readFileSync, writeFileSync } 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 { computeHunkBlocks, getSepStyle, parseDiff, parsePatchFiles, resolveSepStyle, sepLabelSplit, sepLabelUnified } from "./core/diff.js";
|
|
28
27
|
import { configIndicatorStyle } from "./core/config.js";
|
|
28
|
+
import { computeHunkBlocks, getSepStyle, parseDiff, parsePatchFiles, resolveSepStyle, sepLabelSplit, sepLabelUnified, } from "./core/diff.js";
|
|
29
|
+
import { replace } from "./core/replace.js";
|
|
29
30
|
import { resolveLinesFromPatch } from "./core/resolve-lines.js";
|
|
30
31
|
import { registerReviewDiffCommand } from "./review/command.js";
|
|
31
32
|
import { applyDiffPalette as applySharedDiffPalette, lang as detectDiffLanguage, renderSplit as renderSharedSplit, resolveDiffColors as resolveSharedDiffColors, themeCacheKey as sharedThemeCacheKey, } from "./review/hunk-preview.js";
|
|
@@ -33,12 +34,12 @@ const DIFF_PRESETS = {
|
|
|
33
34
|
default: {
|
|
34
35
|
name: "default",
|
|
35
36
|
description: "Original pi-diff colors — tuned for dark theme bases (~#1e1e2e)",
|
|
36
|
-
bgAdd: "#
|
|
37
|
-
bgDel: "#
|
|
38
|
-
bgAddHighlight: "#
|
|
39
|
-
bgDelHighlight: "#
|
|
40
|
-
bgGutterAdd: "#
|
|
41
|
-
bgGutterDel: "#
|
|
37
|
+
bgAdd: "#1a3324",
|
|
38
|
+
bgDel: "#3d2020",
|
|
39
|
+
bgAddHighlight: "#2d5c3a",
|
|
40
|
+
bgDelHighlight: "#5c2d2d",
|
|
41
|
+
bgGutterAdd: "#16281e",
|
|
42
|
+
bgGutterDel: "#301c1c",
|
|
42
43
|
bgEmpty: "#121212",
|
|
43
44
|
fgDim: "#505050",
|
|
44
45
|
fgLnum: "#646464",
|
|
@@ -49,12 +50,12 @@ const DIFF_PRESETS = {
|
|
|
49
50
|
midnight: {
|
|
50
51
|
name: "midnight",
|
|
51
52
|
description: "Subtle tints for pure black (#000000) terminal backgrounds",
|
|
52
|
-
bgAdd: "#
|
|
53
|
-
bgDel: "#
|
|
53
|
+
bgAdd: "#101e15",
|
|
54
|
+
bgDel: "#1e1010",
|
|
54
55
|
bgAddHighlight: "#1a3825",
|
|
55
56
|
bgDelHighlight: "#381a1a",
|
|
56
|
-
bgGutterAdd: "#
|
|
57
|
-
bgGutterDel: "#
|
|
57
|
+
bgGutterAdd: "#0c180c",
|
|
58
|
+
bgGutterDel: "#180c0c",
|
|
58
59
|
bgEmpty: "#080808",
|
|
59
60
|
fgDim: "#404040",
|
|
60
61
|
fgLnum: "#505050",
|
|
@@ -65,12 +66,12 @@ const DIFF_PRESETS = {
|
|
|
65
66
|
subtle: {
|
|
66
67
|
name: "subtle",
|
|
67
68
|
description: "Minimal backgrounds — barely-there tints for a clean look",
|
|
68
|
-
bgAdd: "#
|
|
69
|
-
bgDel: "#
|
|
69
|
+
bgAdd: "#0c160c",
|
|
70
|
+
bgDel: "#160c0c",
|
|
70
71
|
bgAddHighlight: "#122818",
|
|
71
72
|
bgDelHighlight: "#281212",
|
|
72
|
-
bgGutterAdd: "#
|
|
73
|
-
bgGutterDel: "#
|
|
73
|
+
bgGutterAdd: "#080e08",
|
|
74
|
+
bgGutterDel: "#0e0808",
|
|
74
75
|
bgEmpty: "#060606",
|
|
75
76
|
fgDim: "#383838",
|
|
76
77
|
fgLnum: "#484848",
|
|
@@ -81,12 +82,12 @@ const DIFF_PRESETS = {
|
|
|
81
82
|
neon: {
|
|
82
83
|
name: "neon",
|
|
83
84
|
description: "Higher contrast backgrounds for better visibility",
|
|
84
|
-
bgAdd: "#
|
|
85
|
-
bgDel: "#
|
|
85
|
+
bgAdd: "#1e3a28",
|
|
86
|
+
bgDel: "#3a1c18",
|
|
86
87
|
bgAddHighlight: "#2d5c3a",
|
|
87
88
|
bgDelHighlight: "#5c2d2d",
|
|
88
|
-
bgGutterAdd: "#
|
|
89
|
-
bgGutterDel: "#
|
|
89
|
+
bgGutterAdd: "#182c1c",
|
|
90
|
+
bgGutterDel: "#2c1410",
|
|
90
91
|
bgEmpty: "#141414",
|
|
91
92
|
fgDim: "#606060",
|
|
92
93
|
fgLnum: "#787878",
|
|
@@ -181,15 +182,15 @@ function autoDeriveBgFromTheme(theme) {
|
|
|
181
182
|
/* no toolErrorBg — use toolSuccessBg/black */
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
|
-
// Line backgrounds —
|
|
185
|
-
BG_ADD = mixBg(addBase, addRgb, 0.
|
|
186
|
-
BG_DEL = mixBg(delBase, delRgb, 0.
|
|
187
|
-
// Word-level highlights — more
|
|
188
|
-
BG_ADD_W = mixBg(addBase, addRgb, 0.
|
|
189
|
-
BG_DEL_W = mixBg(delBase, delRgb, 0.
|
|
190
|
-
// Gutters — subtler than lines (
|
|
191
|
-
BG_GUTTER_ADD = mixBg(addBase, addRgb, 0.
|
|
192
|
-
BG_GUTTER_DEL = mixBg(delBase, delRgb, 0.
|
|
185
|
+
// Line backgrounds — visible accent mixed into the matching tool-state base (15–18%)
|
|
186
|
+
BG_ADD = mixBg(addBase, addRgb, 0.15);
|
|
187
|
+
BG_DEL = mixBg(delBase, delRgb, 0.18);
|
|
188
|
+
// Word-level highlights — more prominent (30–35%)
|
|
189
|
+
BG_ADD_W = mixBg(addBase, addRgb, 0.3);
|
|
190
|
+
BG_DEL_W = mixBg(delBase, delRgb, 0.35);
|
|
191
|
+
// Gutters — slightly subtler than lines (10–12%)
|
|
192
|
+
BG_GUTTER_ADD = mixBg(addBase, addRgb, 0.1);
|
|
193
|
+
BG_GUTTER_DEL = mixBg(delBase, delRgb, 0.12);
|
|
193
194
|
// Empty filler and context — match the success/context base
|
|
194
195
|
BG_EMPTY = BG_BASE;
|
|
195
196
|
// Update RST to re-apply base bg after every reset — prevents black
|
|
@@ -204,7 +205,10 @@ function autoDeriveBgFromTheme(theme) {
|
|
|
204
205
|
}
|
|
205
206
|
/** Load diff theme config from .pi/settings.json (project-level, then global). */
|
|
206
207
|
function loadDiffConfig() {
|
|
207
|
-
const paths = [
|
|
208
|
+
const paths = [
|
|
209
|
+
`${process.cwd()}/.pi/settings.json`,
|
|
210
|
+
`${process.env.HOME ?? ""}/.pi/settings.json`,
|
|
211
|
+
];
|
|
208
212
|
for (const p of paths) {
|
|
209
213
|
try {
|
|
210
214
|
if (existsSync(p)) {
|
|
@@ -362,16 +366,15 @@ const MAX_WRAP_ROWS_NARROW = 1; // <120 cols (truncate, no wrap)
|
|
|
362
366
|
// ANSI
|
|
363
367
|
// ---------------------------------------------------------------------------
|
|
364
368
|
let RST = "\x1b[0m";
|
|
365
|
-
const BOLD = "\x1b[1m";
|
|
366
369
|
const DIM = "\x1b[2m";
|
|
367
370
|
// Subtle diff backgrounds — muted tones to let syntax fg shine through
|
|
368
371
|
// Override via env: DIFF_BG_ADD="#1a3320" etc. (hex "#RRGGBB" format)
|
|
369
|
-
let BG_ADD = envBg("DIFF_BG_ADD", "\x1b[48;2;
|
|
370
|
-
let BG_DEL = envBg("DIFF_BG_DEL", "\x1b[48;2;
|
|
371
|
-
let BG_ADD_W = envBg("DIFF_BG_ADD_HL", "\x1b[48;2;
|
|
372
|
-
let BG_DEL_W = envBg("DIFF_BG_DEL_HL", "\x1b[48;2;
|
|
373
|
-
let BG_GUTTER_ADD = envBg("DIFF_BG_GUTTER_ADD", "\x1b[48;2;
|
|
374
|
-
let BG_GUTTER_DEL = envBg("DIFF_BG_GUTTER_DEL", "\x1b[48;2;
|
|
372
|
+
let BG_ADD = envBg("DIFF_BG_ADD", "\x1b[48;2;30;52;40m"); // visible teal-green
|
|
373
|
+
let BG_DEL = envBg("DIFF_BG_DEL", "\x1b[48;2;60;30;30m"); // visible brown-red
|
|
374
|
+
let BG_ADD_W = envBg("DIFF_BG_ADD_HL", "\x1b[48;2;45;90;60m"); // word-level emphasis
|
|
375
|
+
let BG_DEL_W = envBg("DIFF_BG_DEL_HL", "\x1b[48;2;100;45;45m");
|
|
376
|
+
let BG_GUTTER_ADD = envBg("DIFF_BG_GUTTER_ADD", "\x1b[48;2;24;42;32m");
|
|
377
|
+
let BG_GUTTER_DEL = envBg("DIFF_BG_GUTTER_DEL", "\x1b[48;2;48;28;28m");
|
|
375
378
|
const BG_GUTTER_CTX = ""; // use terminal default bg for context gutters
|
|
376
379
|
let BG_EMPTY = "\x1b[48;2;18;18;18m"; // filler rows when one side is shorter
|
|
377
380
|
// Diff foregrounds — override via env: DIFF_FG_ADD="#50d264" etc.
|
|
@@ -675,7 +678,7 @@ function shouldUseSplit(diff, tw, maxRows = MAX_PREVIEW_LINES) {
|
|
|
675
678
|
return false;
|
|
676
679
|
const nw = Math.max(2, String(Math.max(...diff.lines.map((l) => l.oldNum ?? l.newNum ?? 0), 0)).length);
|
|
677
680
|
const half = Math.floor((tw - 1) / 2); // -1 for center divider
|
|
678
|
-
const gw = nw +
|
|
681
|
+
const gw = nw + 4; // border + num + spaces around sign
|
|
679
682
|
const cw = Math.max(12, half - gw);
|
|
680
683
|
if (cw < SPLIT_MIN_CODE_WIDTH)
|
|
681
684
|
return false;
|
|
@@ -827,8 +830,6 @@ function wordDiffAnalysis(a, b) {
|
|
|
827
830
|
* Uses sorted-range pointer scan instead of Set (avoids O(totalChars) Set creation).
|
|
828
831
|
*/
|
|
829
832
|
function injectBg(ansiLine, ranges, baseBg, hlBg) {
|
|
830
|
-
if (!ranges.length)
|
|
831
|
-
return baseBg + ansiLine + RST;
|
|
832
833
|
let out = baseBg;
|
|
833
834
|
let vis = 0;
|
|
834
835
|
let inHL = false;
|
|
@@ -897,7 +898,7 @@ async function renderUnified(diff, language, max = MAX_RENDER_LINES, dc = DEFAUL
|
|
|
897
898
|
const vis = diff.lines.slice(0, max);
|
|
898
899
|
const tw = termW();
|
|
899
900
|
const nw = Math.max(2, String(Math.max(...vis.map((l) => l.oldNum ?? l.newNum ?? 0), 0)).length);
|
|
900
|
-
const gw = nw +
|
|
901
|
+
const gw = nw + 4; // border + num + spaces around sign
|
|
901
902
|
const cw = Math.max(20, tw - gw);
|
|
902
903
|
const canHL = diff.chars <= MAX_HL_CHARS && vis.length <= MAX_RENDER_LINES;
|
|
903
904
|
// Build separate old/new code blocks for highlighting
|
|
@@ -909,18 +910,20 @@ async function renderUnified(diff, language, max = MAX_RENDER_LINES, dc = DEFAUL
|
|
|
909
910
|
newSrc.push(l.content);
|
|
910
911
|
}
|
|
911
912
|
const [oldHL, newHL] = canHL
|
|
912
|
-
? await Promise.all([
|
|
913
|
+
? await Promise.all([
|
|
914
|
+
hlBlock(oldSrc.join("\n"), language),
|
|
915
|
+
hlBlock(newSrc.join("\n"), language),
|
|
916
|
+
])
|
|
913
917
|
: [oldSrc, newSrc];
|
|
914
918
|
let oI = 0, nI = 0, idx = 0;
|
|
915
919
|
const out = [];
|
|
916
|
-
out.push(rule(tw));
|
|
917
920
|
/** Emit a single stacked row with compact gutter + left border bar. */
|
|
918
921
|
function emitRow(num, sign, gutterBg, signFg, body, bodyBg = "") {
|
|
919
922
|
const borderFg = sign === "-" ? dc.fgDel : sign === "+" ? dc.fgAdd : "";
|
|
920
923
|
const border = borderFg ? `${borderFg}${getBorderBar()}${RST}` : `${BG_BASE} `;
|
|
921
924
|
const numFg = borderFg || FG_LNUM;
|
|
922
|
-
const gutter = `${border}${gutterBg}${lnum(num, nw, numFg)}${signFg}${sign}${
|
|
923
|
-
const contGutter = `${border}${gutterBg}${" ".repeat(nw +
|
|
925
|
+
const gutter = `${border}${gutterBg}${lnum(num, nw, numFg)}${gutterBg} ${signFg}${sign}${gutterBg} ${RST}`;
|
|
926
|
+
const contGutter = `${border}${gutterBg}${" ".repeat(nw + 3)}${RST}`;
|
|
924
927
|
const rows = wrapAnsi(tabs(body), cw, adaptiveWrapRows(), bodyBg);
|
|
925
928
|
out.push(`${gutter}${rows[0]}${RST}`);
|
|
926
929
|
for (let r = 1; r < rows.length; r++)
|
|
@@ -971,27 +974,26 @@ async function renderUnified(diff, language, max = MAX_RENDER_LINES, dc = DEFAUL
|
|
|
971
974
|
if (isPaired && wdBalanced && wd.similarity >= WORD_DIFF_MIN_SIM && canHL) {
|
|
972
975
|
const delBody = injectBg(dels[0].hl, wd.oldRanges, BG_DEL, BG_DEL_W);
|
|
973
976
|
const addBody = injectBg(adds[0].hl, wd.newRanges, BG_ADD, BG_ADD_W);
|
|
974
|
-
emitRow(dels[0].l.oldNum, "-", BG_GUTTER_DEL,
|
|
975
|
-
emitRow(adds[0].l.newNum, "+", BG_GUTTER_ADD,
|
|
977
|
+
emitRow(dels[0].l.oldNum, "-", BG_GUTTER_DEL, dc.fgDel, delBody, BG_DEL);
|
|
978
|
+
emitRow(adds[0].l.newNum, "+", BG_GUTTER_ADD, dc.fgAdd, addBody, BG_ADD);
|
|
976
979
|
continue;
|
|
977
980
|
}
|
|
978
981
|
if (isPaired && wdBalanced && wd.similarity >= WORD_DIFF_MIN_SIM && !canHL) {
|
|
979
982
|
const pwd = plainWordDiff(dels[0].l.content, adds[0].l.content);
|
|
980
|
-
emitRow(dels[0].l.oldNum, "-", BG_GUTTER_DEL,
|
|
981
|
-
emitRow(adds[0].l.newNum, "+", BG_GUTTER_ADD,
|
|
983
|
+
emitRow(dels[0].l.oldNum, "-", BG_GUTTER_DEL, dc.fgDel, `${BG_DEL}${pwd.old}`, BG_DEL);
|
|
984
|
+
emitRow(adds[0].l.newNum, "+", BG_GUTTER_ADD, dc.fgAdd, `${BG_ADD}${pwd.new}`, BG_ADD);
|
|
982
985
|
continue;
|
|
983
986
|
}
|
|
984
987
|
// Multi-line blocks — syntax highlighted with diff bg
|
|
985
988
|
for (const d of dels) {
|
|
986
|
-
const body = canHL ?
|
|
987
|
-
emitRow(d.l.oldNum, "-", BG_GUTTER_DEL,
|
|
989
|
+
const body = canHL ? injectBg(d.hl, [], BG_DEL, BG_DEL) : `${BG_DEL}${d.l.content}`;
|
|
990
|
+
emitRow(d.l.oldNum, "-", BG_GUTTER_DEL, dc.fgDel, body, BG_DEL);
|
|
988
991
|
}
|
|
989
992
|
for (const a of adds) {
|
|
990
|
-
const body = canHL ?
|
|
991
|
-
emitRow(a.l.newNum, "+", BG_GUTTER_ADD,
|
|
993
|
+
const body = canHL ? injectBg(a.hl, [], BG_ADD, BG_ADD) : `${BG_ADD}${a.l.content}`;
|
|
994
|
+
emitRow(a.l.newNum, "+", BG_GUTTER_ADD, dc.fgAdd, body, BG_ADD);
|
|
992
995
|
}
|
|
993
996
|
}
|
|
994
|
-
out.push(rule(tw));
|
|
995
997
|
if (diff.lines.length > vis.length) {
|
|
996
998
|
out.push(`${BG_BASE}${FG_DIM} … ${diff.lines.length - vis.length} more lines${RST}`);
|
|
997
999
|
}
|
|
@@ -1038,7 +1040,7 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1038
1040
|
const vis = rows.slice(0, max);
|
|
1039
1041
|
const half = Math.floor((tw - 1) / 2); // -1 for center divider
|
|
1040
1042
|
const nw = Math.max(2, String(Math.max(...diff.lines.map((l) => l.oldNum ?? l.newNum ?? 0), 0)).length);
|
|
1041
|
-
const gw = nw +
|
|
1043
|
+
const gw = nw + 4; // border + num + spaces around sign
|
|
1042
1044
|
const cw = Math.max(12, half - gw);
|
|
1043
1045
|
const canHL = diff.chars <= MAX_HL_CHARS && vis.length * 2 <= MAX_RENDER_LINES * 2;
|
|
1044
1046
|
// Build separate code blocks per side
|
|
@@ -1050,22 +1052,25 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1050
1052
|
rightSrc.push(r.right.content);
|
|
1051
1053
|
}
|
|
1052
1054
|
const [leftHL, rightHL] = canHL
|
|
1053
|
-
? await Promise.all([
|
|
1055
|
+
? await Promise.all([
|
|
1056
|
+
hlBlock(leftSrc.join("\n"), language),
|
|
1057
|
+
hlBlock(rightSrc.join("\n"), language),
|
|
1058
|
+
])
|
|
1054
1059
|
: [leftSrc, rightSrc];
|
|
1055
1060
|
let lI = 0, rI = 0;
|
|
1056
1061
|
let stripeRow = 0; // tracks row index for diagonal stripe offset
|
|
1057
1062
|
function half_build(line, hl, ranges, side) {
|
|
1058
1063
|
// Empty filler — diagonal stripes
|
|
1059
1064
|
if (!line) {
|
|
1060
|
-
const gw2 = nw +
|
|
1065
|
+
const gw2 = nw + 4; // border + number + spaces around sign
|
|
1061
1066
|
const gPat = FG_STRIPE + "╱".repeat(gw2) + RST;
|
|
1062
|
-
const g =
|
|
1067
|
+
const g = gPat;
|
|
1063
1068
|
return { gutter: g, contGutter: g, bodyRows: [stripes(cw, stripeRow)] };
|
|
1064
1069
|
}
|
|
1065
1070
|
// Hunk separator with optional function context
|
|
1066
1071
|
if (line.type === "sep") {
|
|
1067
1072
|
const label = sepLabelSplit(getSepStyle(), line.hunkMeta, line.newNum, line.content);
|
|
1068
|
-
const g = `${BG_BASE} ${FG_DIM}${fit("", nw +
|
|
1073
|
+
const g = `${BG_BASE} ${FG_DIM}${fit("", nw + 3)}${RST}`;
|
|
1069
1074
|
return { gutter: g, contGutter: g, bodyRows: [`${BG_BASE}${FG_DIM}${fit(label, cw)}${RST}`] };
|
|
1070
1075
|
}
|
|
1071
1076
|
const isDel = line.type === "del", isAdd = line.type === "add";
|
|
@@ -1073,7 +1078,13 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1073
1078
|
const cBg = isDel ? BG_DEL : isAdd ? BG_ADD : BG_BASE;
|
|
1074
1079
|
const sFg = isDel ? dc.fgDel : isAdd ? dc.fgAdd : dc.fgCtx;
|
|
1075
1080
|
const sign = isDel ? "-" : isAdd ? "+" : " ";
|
|
1076
|
-
const num = isDel
|
|
1081
|
+
const num = isDel
|
|
1082
|
+
? line.oldNum
|
|
1083
|
+
: isAdd
|
|
1084
|
+
? line.newNum
|
|
1085
|
+
: side === "left"
|
|
1086
|
+
? line.oldNum
|
|
1087
|
+
: line.newNum;
|
|
1077
1088
|
// Border bar + colored line numbers for changed lines
|
|
1078
1089
|
const borderFg = isDel ? dc.fgDel : isAdd ? dc.fgAdd : "";
|
|
1079
1090
|
const border = borderFg ? `${borderFg}${getBorderBar()}${RST}` : ` ${BG_BASE}`;
|
|
@@ -1083,13 +1094,13 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1083
1094
|
body = injectBg(hl, ranges, cBg, isDel ? BG_DEL_W : BG_ADD_W);
|
|
1084
1095
|
}
|
|
1085
1096
|
else if (isDel || isAdd) {
|
|
1086
|
-
body =
|
|
1097
|
+
body = injectBg(hl, [], cBg, cBg);
|
|
1087
1098
|
}
|
|
1088
1099
|
else {
|
|
1089
1100
|
body = `${BG_BASE}${DIM}${hl}`;
|
|
1090
1101
|
}
|
|
1091
|
-
const gutter = `${border}${gBg}${lnum(num, nw, numFg)}${
|
|
1092
|
-
const contGutter = `${border}${gBg}${" ".repeat(nw +
|
|
1102
|
+
const gutter = `${border}${gBg}${lnum(num, nw, numFg)}${gBg} ${sFg}${sign}${gBg} ${RST}`;
|
|
1103
|
+
const contGutter = `${border}${gBg}${" ".repeat(nw + 3)}${RST}`;
|
|
1093
1104
|
const bodyRows = wrapAnsi(tabs(body), cw, adaptiveWrapRows(), cBg);
|
|
1094
1105
|
return { gutter, contGutter, bodyRows };
|
|
1095
1106
|
}
|
|
@@ -1098,7 +1109,6 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1098
1109
|
const hdrOld = `${BG_BASE}${" ".repeat(Math.max(0, nw - 2))}${dc.fgDel}${DIM}old${RST}`;
|
|
1099
1110
|
const hdrNew = `${BG_BASE}${" ".repeat(Math.max(0, nw - 2))}${dc.fgAdd}${DIM}new${RST}`;
|
|
1100
1111
|
out.push(`${BG_BASE}${hdrOld}${" ".repeat(Math.max(0, half - nw - 1))}${FG_RULE}┊${RST}${hdrNew}`);
|
|
1101
|
-
out.push(`${rule(half)}${FG_RULE}┊${RST}${rule(half)}`);
|
|
1102
1112
|
for (const r of vis) {
|
|
1103
1113
|
const leftLine = r.left, rightLine = r.right;
|
|
1104
1114
|
const paired = leftLine && rightLine && leftLine.type === "del" && rightLine.type === "add";
|
|
@@ -1130,13 +1140,14 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1130
1140
|
for (let row = 0; row < maxRows; row++) {
|
|
1131
1141
|
const lg = row === 0 ? lResult.gutter : lResult.contGutter;
|
|
1132
1142
|
const rg = row === 0 ? rResult.gutter : rResult.contGutter;
|
|
1133
|
-
const lb = lResult.bodyRows[row] ??
|
|
1134
|
-
|
|
1143
|
+
const lb = lResult.bodyRows[row] ??
|
|
1144
|
+
(leftIsEmpty ? stripes(cw, stripeRow) : `${BG_EMPTY}${" ".repeat(cw)}${RST}`);
|
|
1145
|
+
const rb = rResult.bodyRows[row] ??
|
|
1146
|
+
(rightIsEmpty ? stripes(cw, stripeRow) : `${BG_EMPTY}${" ".repeat(cw)}${RST}`);
|
|
1135
1147
|
out.push(`${lg}${lb}${DIVIDER}${rg}${rb}`);
|
|
1136
1148
|
stripeRow++;
|
|
1137
1149
|
}
|
|
1138
1150
|
}
|
|
1139
|
-
out.push(`${rule(half)}${FG_RULE}┊${RST}${rule(half)}`);
|
|
1140
1151
|
if (rows.length > vis.length) {
|
|
1141
1152
|
out.push(`${BG_BASE}${FG_DIM} … ${rows.length - vis.length} more lines${RST}`);
|
|
1142
1153
|
}
|
|
@@ -1179,6 +1190,70 @@ export default async function diffRendererExtension(pi) {
|
|
|
1179
1190
|
const cwd = process.cwd();
|
|
1180
1191
|
const home = process.env.HOME ?? "";
|
|
1181
1192
|
const sp = (p) => shortPath(cwd, home, p);
|
|
1193
|
+
const TOOL_HEADER_LEFT_PAD = 2;
|
|
1194
|
+
const TOOL_HEADER_TOP_PAD = 1;
|
|
1195
|
+
const TOOL_PREVIEW_BOTTOM_PAD = 1;
|
|
1196
|
+
const DIFF_BODY_LEFT_PAD = 1;
|
|
1197
|
+
function resolvePreviewDiffColors(theme) {
|
|
1198
|
+
resolveDiffColors(theme);
|
|
1199
|
+
return resolveSharedDiffColors(theme);
|
|
1200
|
+
}
|
|
1201
|
+
function bgLine(content, width) {
|
|
1202
|
+
const renderWidth = Math.max(1, width);
|
|
1203
|
+
const padding = " ".repeat(Math.max(0, renderWidth - strip(content).length));
|
|
1204
|
+
return injectBg(`${content}${padding}`, [], BG_BASE, BG_BASE);
|
|
1205
|
+
}
|
|
1206
|
+
function formatToolHeader(summary, width) {
|
|
1207
|
+
const leftPad = " ".repeat(TOOL_HEADER_LEFT_PAD);
|
|
1208
|
+
const meta = `${leftPad}${summary}`;
|
|
1209
|
+
return bgLine(meta, width);
|
|
1210
|
+
}
|
|
1211
|
+
function formatToolTitle(label, filePath, theme, width, suffix = "") {
|
|
1212
|
+
const leftPad = " ".repeat(TOOL_HEADER_LEFT_PAD);
|
|
1213
|
+
const displayPath = filePath ? ` ${theme.fg("accent", sp(filePath))}` : "";
|
|
1214
|
+
const top = Array.from({ length: TOOL_HEADER_TOP_PAD }, () => bgLine("", width));
|
|
1215
|
+
return [
|
|
1216
|
+
...top,
|
|
1217
|
+
bgLine(`${leftPad}${theme.fg("toolTitle", theme.bold(label))}${displayPath}${suffix}`, width),
|
|
1218
|
+
].join("\n");
|
|
1219
|
+
}
|
|
1220
|
+
function formatBottomPadding(width) {
|
|
1221
|
+
return Array.from({ length: TOOL_PREVIEW_BOTTOM_PAD }, () => bgLine("", width)).join("\n");
|
|
1222
|
+
}
|
|
1223
|
+
function padDiffBody(rendered) {
|
|
1224
|
+
const leftPad = `${BG_BASE}${" ".repeat(DIFF_BODY_LEFT_PAD)}${RST}`;
|
|
1225
|
+
return rendered
|
|
1226
|
+
.split("\n")
|
|
1227
|
+
.map((line) => `${leftPad}${line}`)
|
|
1228
|
+
.join("\n");
|
|
1229
|
+
}
|
|
1230
|
+
async function renderPaddedDiff(diff, language, maxLines, colors, width) {
|
|
1231
|
+
const bodyWidth = Math.max(1, width - DIFF_BODY_LEFT_PAD);
|
|
1232
|
+
return padDiffBody(await renderSharedSplit(diff, language, maxLines, colors, bodyWidth));
|
|
1233
|
+
}
|
|
1234
|
+
function diffLineCountLabel(diffLineCount, theme) {
|
|
1235
|
+
if (typeof diffLineCount !== "number")
|
|
1236
|
+
return "";
|
|
1237
|
+
return ` ${theme.fg("muted", `(${diffLineCount} diff lines)`)}`;
|
|
1238
|
+
}
|
|
1239
|
+
function setToolHeaderText(text, meta, theme) {
|
|
1240
|
+
resolvePreviewDiffColors(theme);
|
|
1241
|
+
text.__piDiffTask = undefined;
|
|
1242
|
+
const width = termW();
|
|
1243
|
+
text.setText(`${formatToolHeader(meta, width)}\n${formatBottomPadding(width)}`);
|
|
1244
|
+
}
|
|
1245
|
+
function setDiffPreviewTask(text, keyPrefix, meta, diff, language, maxLines, theme, ctx) {
|
|
1246
|
+
const themeKey = sharedThemeCacheKey(theme);
|
|
1247
|
+
const colors = resolvePreviewDiffColors(theme);
|
|
1248
|
+
const header = (width) => formatToolHeader(meta, width);
|
|
1249
|
+
text.__piDiffTask = {
|
|
1250
|
+
placeholder: `${header(termW())}\n${padDiffBody(theme.fg("muted", "rendering diff…"))}\n${formatBottomPadding(termW())}`,
|
|
1251
|
+
fallback: `${header(termW())}\n${formatBottomPadding(termW())}`,
|
|
1252
|
+
invalidate: ctx.invalidate,
|
|
1253
|
+
key: (width) => `${keyPrefix}:${themeKey}:${width}:${meta}:${diff.lines.length}:${language ?? ""}`,
|
|
1254
|
+
render: async (width) => `${header(width)}\n${await renderPaddedDiff(diff, language, maxLines, colors, width)}\n${formatBottomPadding(width)}`,
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1182
1257
|
/** Wrap a Text component so its render(width) kicks off async diff rendering
|
|
1183
1258
|
* using the real TUI width (which accounts for the sidebar). */
|
|
1184
1259
|
function getWidthAwareText(lastComponent) {
|
|
@@ -1213,7 +1288,7 @@ export default async function diffRendererExtension(pi) {
|
|
|
1213
1288
|
});
|
|
1214
1289
|
}
|
|
1215
1290
|
}
|
|
1216
|
-
return text.__piDiffRender(width);
|
|
1291
|
+
return text.__piDiffRender?.(width) ?? [];
|
|
1217
1292
|
};
|
|
1218
1293
|
return text;
|
|
1219
1294
|
}
|
|
@@ -1262,7 +1337,11 @@ Examples:
|
|
|
1262
1337
|
try {
|
|
1263
1338
|
const { existingCode, filePath, patchText, fileContent } = params ?? {};
|
|
1264
1339
|
if (!existingCode || !filePath) {
|
|
1265
|
-
return {
|
|
1340
|
+
return {
|
|
1341
|
+
content: [
|
|
1342
|
+
{ type: "text", text: "Error: existingCode and filePath are required" },
|
|
1343
|
+
],
|
|
1344
|
+
};
|
|
1266
1345
|
}
|
|
1267
1346
|
// Fetch git diff if patchText not provided
|
|
1268
1347
|
let patch = patchText;
|
|
@@ -1288,25 +1367,42 @@ Examples:
|
|
|
1288
1367
|
}
|
|
1289
1368
|
catch {
|
|
1290
1369
|
return {
|
|
1291
|
-
content: [
|
|
1370
|
+
content: [
|
|
1371
|
+
{ type: "text", text: `Error: could not read git diff for ${filePath}` },
|
|
1372
|
+
],
|
|
1292
1373
|
};
|
|
1293
1374
|
}
|
|
1294
1375
|
}
|
|
1295
1376
|
if (!patch) {
|
|
1296
1377
|
return {
|
|
1297
|
-
content: [
|
|
1378
|
+
content: [
|
|
1379
|
+
{
|
|
1380
|
+
type: "text",
|
|
1381
|
+
text: `No diff found for ${filePath} — file may not have changes`,
|
|
1382
|
+
},
|
|
1383
|
+
],
|
|
1298
1384
|
details: { unresolved: true },
|
|
1299
1385
|
};
|
|
1300
1386
|
}
|
|
1301
1387
|
const result = resolveLinesFromPatch(existingCode, patch, fileContent);
|
|
1302
1388
|
if ("unresolved" in result) {
|
|
1303
1389
|
return {
|
|
1304
|
-
content: [
|
|
1390
|
+
content: [
|
|
1391
|
+
{
|
|
1392
|
+
type: "text",
|
|
1393
|
+
text: `Could not resolve line numbers for existing_code in ${filePath}`,
|
|
1394
|
+
},
|
|
1395
|
+
],
|
|
1305
1396
|
details: { unresolved: true },
|
|
1306
1397
|
};
|
|
1307
1398
|
}
|
|
1308
1399
|
return {
|
|
1309
|
-
content: [
|
|
1400
|
+
content: [
|
|
1401
|
+
{
|
|
1402
|
+
type: "text",
|
|
1403
|
+
text: `Resolved ${filePath}:${result.startLine}-${result.endLine}`,
|
|
1404
|
+
},
|
|
1405
|
+
],
|
|
1310
1406
|
details: { startLine: result.startLine, endLine: result.endLine },
|
|
1311
1407
|
};
|
|
1312
1408
|
}
|
|
@@ -1366,11 +1462,24 @@ Examples:
|
|
|
1366
1462
|
const useFull = !!params._expandGaps;
|
|
1367
1463
|
const diff = parseDiff(old, content, useFull ? undefined : 3);
|
|
1368
1464
|
const lg = detectDiffLanguage(fp);
|
|
1369
|
-
result.details = {
|
|
1465
|
+
result.details = {
|
|
1466
|
+
_type: "diff",
|
|
1467
|
+
summary: summarize(diff.added, diff.removed),
|
|
1468
|
+
filePath: fp,
|
|
1469
|
+
diff,
|
|
1470
|
+
language: lg,
|
|
1471
|
+
oldContent: old,
|
|
1472
|
+
newContent: content,
|
|
1473
|
+
};
|
|
1370
1474
|
}
|
|
1371
1475
|
else if (old === null) {
|
|
1372
1476
|
const lineCount = content ? content.split("\n").length : 0;
|
|
1373
|
-
result.details = {
|
|
1477
|
+
result.details = {
|
|
1478
|
+
_type: "new",
|
|
1479
|
+
lines: lineCount,
|
|
1480
|
+
content: content ?? "",
|
|
1481
|
+
filePath: fp,
|
|
1482
|
+
};
|
|
1374
1483
|
}
|
|
1375
1484
|
else if (old === content) {
|
|
1376
1485
|
result.details = { _type: "noChange" };
|
|
@@ -1430,15 +1539,7 @@ Examples:
|
|
|
1430
1539
|
}
|
|
1431
1540
|
const d = result.details;
|
|
1432
1541
|
if (d?._type === "diff") {
|
|
1433
|
-
|
|
1434
|
-
const dc = resolveSharedDiffColors(theme);
|
|
1435
|
-
text.__piDiffTask = {
|
|
1436
|
-
placeholder: ` ${d.summary}\n${theme.fg("muted", " rendering diff…")}`,
|
|
1437
|
-
fallback: ` ${d.summary}`,
|
|
1438
|
-
invalidate: ctx.invalidate,
|
|
1439
|
-
key: (width) => `wd:${themeKey}:${width}:${d.summary}:${d.diff?.lines?.length ?? 0}:${d.language ?? ""}`,
|
|
1440
|
-
render: async (width) => ` ${d.summary}\n${await renderSharedSplit(d.diff, d.language, MAX_RENDER_LINES, dc, width)}`,
|
|
1441
|
-
};
|
|
1542
|
+
setDiffPreviewTask(text, "wd", d.summary, d.diff, d.language, MAX_RENDER_LINES, theme, ctx);
|
|
1442
1543
|
return text;
|
|
1443
1544
|
}
|
|
1444
1545
|
if (d?._type === "noChange") {
|
|
@@ -1485,13 +1586,29 @@ Examples:
|
|
|
1485
1586
|
if (Array.isArray(input?.edits)) {
|
|
1486
1587
|
return input.edits
|
|
1487
1588
|
.map((edit) => ({
|
|
1488
|
-
oldText: typeof edit?.oldText === "string"
|
|
1489
|
-
|
|
1589
|
+
oldText: typeof edit?.oldText === "string"
|
|
1590
|
+
? edit.oldText
|
|
1591
|
+
: typeof edit?.old_text === "string"
|
|
1592
|
+
? edit.old_text
|
|
1593
|
+
: "",
|
|
1594
|
+
newText: typeof edit?.newText === "string"
|
|
1595
|
+
? edit.newText
|
|
1596
|
+
: typeof edit?.new_text === "string"
|
|
1597
|
+
? edit.new_text
|
|
1598
|
+
: "",
|
|
1490
1599
|
}))
|
|
1491
1600
|
.filter((edit) => edit.oldText && edit.oldText !== edit.newText);
|
|
1492
1601
|
}
|
|
1493
|
-
const oldText = typeof input?.oldText === "string"
|
|
1494
|
-
|
|
1602
|
+
const oldText = typeof input?.oldText === "string"
|
|
1603
|
+
? input.oldText
|
|
1604
|
+
: typeof input?.old_text === "string"
|
|
1605
|
+
? input.old_text
|
|
1606
|
+
: "";
|
|
1607
|
+
const newText = typeof input?.newText === "string"
|
|
1608
|
+
? input.newText
|
|
1609
|
+
: typeof input?.new_text === "string"
|
|
1610
|
+
? input.new_text
|
|
1611
|
+
: "";
|
|
1495
1612
|
return oldText && oldText !== newText ? [{ oldText, newText }] : [];
|
|
1496
1613
|
}
|
|
1497
1614
|
function summarizeEditOperations(operations) {
|
|
@@ -1511,6 +1628,95 @@ Examples:
|
|
|
1511
1628
|
async execute(tid, params, sig, upd, ctx) {
|
|
1512
1629
|
const fp = params.path ?? params.file_path ?? "";
|
|
1513
1630
|
const operations = getEditOperations(params);
|
|
1631
|
+
// Try cascading replace() first — smarter matching than SDK's exact-only edit
|
|
1632
|
+
if (fp && operations.length > 0 && existsSync(fp)) {
|
|
1633
|
+
try {
|
|
1634
|
+
let content = readFileSync(fp, "utf-8");
|
|
1635
|
+
let firstStrategy = "";
|
|
1636
|
+
let replaceOk = true;
|
|
1637
|
+
for (const op of operations) {
|
|
1638
|
+
const r = replace(content, op.oldText, op.newText);
|
|
1639
|
+
if (r.changed) {
|
|
1640
|
+
content = r.content;
|
|
1641
|
+
if (!firstStrategy)
|
|
1642
|
+
firstStrategy = r.strategy;
|
|
1643
|
+
}
|
|
1644
|
+
else {
|
|
1645
|
+
replaceOk = false;
|
|
1646
|
+
break;
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
if (replaceOk) {
|
|
1650
|
+
writeFileSync(fp, content, "utf-8");
|
|
1651
|
+
const { diffs, summary } = summarizeEditOperations(operations);
|
|
1652
|
+
const lg = detectDiffLanguage(fp);
|
|
1653
|
+
if (operations.length === 1) {
|
|
1654
|
+
let editLine = 0;
|
|
1655
|
+
try {
|
|
1656
|
+
const idx = content.indexOf(operations[0].newText);
|
|
1657
|
+
if (idx >= 0)
|
|
1658
|
+
editLine = content.slice(0, idx).split("\n").length;
|
|
1659
|
+
}
|
|
1660
|
+
catch {
|
|
1661
|
+
editLine = 0;
|
|
1662
|
+
}
|
|
1663
|
+
const useFull = !!params._expandGaps;
|
|
1664
|
+
const diffData = useFull
|
|
1665
|
+
? parseDiff(operations[0].oldText, operations[0].newText, undefined)
|
|
1666
|
+
: diffs[0];
|
|
1667
|
+
return {
|
|
1668
|
+
content: [{ type: "text", text: `Edited ${sp(fp)}` }],
|
|
1669
|
+
details: {
|
|
1670
|
+
_type: "editInfo",
|
|
1671
|
+
summary,
|
|
1672
|
+
filePath: fp,
|
|
1673
|
+
editLine,
|
|
1674
|
+
diff: diffData,
|
|
1675
|
+
language: lg,
|
|
1676
|
+
oldContent: operations[0].oldText,
|
|
1677
|
+
newContent: operations[0].newText,
|
|
1678
|
+
_replaceStrategy: firstStrategy,
|
|
1679
|
+
},
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
// Merge all diffs into one combined view for rendering
|
|
1683
|
+
const merged = {
|
|
1684
|
+
lines: diffs.flatMap((diff, i) => [
|
|
1685
|
+
...(i > 0
|
|
1686
|
+
? [
|
|
1687
|
+
{
|
|
1688
|
+
type: "sep",
|
|
1689
|
+
oldNum: null,
|
|
1690
|
+
newNum: null,
|
|
1691
|
+
content: `───── Edit ${i + 1} ─────`,
|
|
1692
|
+
},
|
|
1693
|
+
]
|
|
1694
|
+
: []),
|
|
1695
|
+
...diff.lines,
|
|
1696
|
+
]),
|
|
1697
|
+
added: diffs.reduce((sum, diff) => sum + diff.added, 0),
|
|
1698
|
+
removed: diffs.reduce((sum, diff) => sum + diff.removed, 0),
|
|
1699
|
+
chars: diffs.reduce((sum, diff) => sum + diff.chars, 0),
|
|
1700
|
+
};
|
|
1701
|
+
return {
|
|
1702
|
+
content: [{ type: "text", text: `Edited ${sp(fp)}` }],
|
|
1703
|
+
details: {
|
|
1704
|
+
_type: "multiEditInfo",
|
|
1705
|
+
summary,
|
|
1706
|
+
filePath: fp,
|
|
1707
|
+
editCount: operations.length,
|
|
1708
|
+
diffLineCount: merged.lines.length,
|
|
1709
|
+
diff: merged,
|
|
1710
|
+
language: lg,
|
|
1711
|
+
},
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
catch (replaceError) {
|
|
1716
|
+
// replace() failed; fall through to SDK edit tool
|
|
1717
|
+
console.warn(`[pi-diff] replace() failed, falling back to SDK: ${replaceError instanceof Error ? replaceError.message : String(replaceError)}`);
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1514
1720
|
const result = await origEdit.execute(tid, params, sig, upd, ctx);
|
|
1515
1721
|
if (operations.length === 0)
|
|
1516
1722
|
return result;
|
|
@@ -1536,6 +1742,7 @@ Examples:
|
|
|
1536
1742
|
result.details = {
|
|
1537
1743
|
_type: "editInfo",
|
|
1538
1744
|
summary,
|
|
1745
|
+
filePath: fp,
|
|
1539
1746
|
editLine,
|
|
1540
1747
|
diff: diffData,
|
|
1541
1748
|
language: lg,
|
|
@@ -1548,7 +1755,16 @@ Examples:
|
|
|
1548
1755
|
const merged = {
|
|
1549
1756
|
lines: diffs.flatMap((diff, i) => [
|
|
1550
1757
|
// Add separator between multiple edits
|
|
1551
|
-
...(i > 0
|
|
1758
|
+
...(i > 0
|
|
1759
|
+
? [
|
|
1760
|
+
{
|
|
1761
|
+
type: "sep",
|
|
1762
|
+
oldNum: null,
|
|
1763
|
+
newNum: null,
|
|
1764
|
+
content: `───── Edit ${i + 1} ─────`,
|
|
1765
|
+
},
|
|
1766
|
+
]
|
|
1767
|
+
: []),
|
|
1552
1768
|
...diff.lines,
|
|
1553
1769
|
]),
|
|
1554
1770
|
added: diffs.reduce((sum, diff) => sum + diff.added, 0),
|
|
@@ -1558,6 +1774,7 @@ Examples:
|
|
|
1558
1774
|
result.details = {
|
|
1559
1775
|
_type: "multiEditInfo",
|
|
1560
1776
|
summary,
|
|
1777
|
+
filePath: fp,
|
|
1561
1778
|
editCount: operations.length,
|
|
1562
1779
|
diffLineCount: merged.lines.length,
|
|
1563
1780
|
diff: merged,
|
|
@@ -1569,13 +1786,13 @@ Examples:
|
|
|
1569
1786
|
const fp = args?.path ?? args?.file_path ?? "";
|
|
1570
1787
|
const operations = getEditOperations(args);
|
|
1571
1788
|
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
1572
|
-
|
|
1789
|
+
resolvePreviewDiffColors(theme);
|
|
1573
1790
|
if (ctx.argsComplete && operations.length > 0) {
|
|
1574
1791
|
const { totalAdded, totalRemoved } = summarizeEditOperations(operations);
|
|
1575
|
-
text.setText(
|
|
1792
|
+
text.setText(formatToolTitle("edit", fp, theme, termW(), ` ${theme.fg("muted", summarize(totalAdded, totalRemoved))}`));
|
|
1576
1793
|
}
|
|
1577
1794
|
else {
|
|
1578
|
-
text.setText(
|
|
1795
|
+
text.setText(formatToolTitle("edit", fp, theme, termW()));
|
|
1579
1796
|
}
|
|
1580
1797
|
return text;
|
|
1581
1798
|
},
|
|
@@ -1592,42 +1809,24 @@ Examples:
|
|
|
1592
1809
|
}
|
|
1593
1810
|
const d = result.details;
|
|
1594
1811
|
if (d?._type === "editInfo" && d.diff) {
|
|
1595
|
-
const themeKey = sharedThemeCacheKey(theme);
|
|
1596
|
-
const dc = resolveSharedDiffColors(theme);
|
|
1597
1812
|
const loc = d.editLine > 0 ? ` ${theme.fg("muted", `at line ${d.editLine}`)}` : "";
|
|
1598
|
-
text.
|
|
1599
|
-
placeholder: ` ${d.summary}${loc}\n${theme.fg("muted", " rendering diff…")}`,
|
|
1600
|
-
fallback: ` ${d.summary}${loc}`,
|
|
1601
|
-
invalidate: ctx.invalidate,
|
|
1602
|
-
key: (width) => `ed:${themeKey}:${width}:${d.summary}:${d.diff?.lines?.length ?? 0}:${d.language ?? ""}`,
|
|
1603
|
-
render: async (width) => ` ${d.summary}${loc}\n${await renderSharedSplit(d.diff, d.language, MAX_PREVIEW_LINES, dc, width)}`,
|
|
1604
|
-
};
|
|
1813
|
+
setDiffPreviewTask(text, "ed", `${d.summary}${loc}`, d.diff, d.language, MAX_PREVIEW_LINES, theme, ctx);
|
|
1605
1814
|
return text;
|
|
1606
1815
|
}
|
|
1607
1816
|
if (d?._type === "editInfo") {
|
|
1608
1817
|
const { summary: s, editLine } = d;
|
|
1609
1818
|
const loc = editLine > 0 ? ` ${theme.fg("muted", `at line ${editLine}`)}` : "";
|
|
1610
|
-
text
|
|
1611
|
-
text.setText(` ${s}${loc}`);
|
|
1819
|
+
setToolHeaderText(text, `${s}${loc}`, theme);
|
|
1612
1820
|
return text;
|
|
1613
1821
|
}
|
|
1614
1822
|
if (d?._type === "multiEditInfo") {
|
|
1615
1823
|
const { summary: s, editCount, diffLineCount, diff, language } = d;
|
|
1824
|
+
const meta = `${editCount} edits ${s}${diffLineCountLabel(diffLineCount, theme)}`;
|
|
1616
1825
|
if (diff) {
|
|
1617
|
-
|
|
1618
|
-
const dc = resolveSharedDiffColors(theme);
|
|
1619
|
-
text.__piDiffTask = {
|
|
1620
|
-
placeholder: ` ${editCount} edits ${s}
|
|
1621
|
-
${theme.fg("muted", " rendering diff…")}`,
|
|
1622
|
-
fallback: ` ${editCount} edits ${s}${typeof diffLineCount === "number" ? ` ${theme.fg("muted", `(${diffLineCount} diff lines)`)}` : ""}`,
|
|
1623
|
-
invalidate: ctx.invalidate,
|
|
1624
|
-
key: (width) => `me:${themeKey}:${width}:${s}:${diff?.lines?.length ?? 0}:${language ?? ""}`,
|
|
1625
|
-
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)}`,
|
|
1626
|
-
};
|
|
1826
|
+
setDiffPreviewTask(text, "me", meta, diff, language, MAX_PREVIEW_LINES, theme, ctx);
|
|
1627
1827
|
return text;
|
|
1628
1828
|
}
|
|
1629
|
-
text
|
|
1630
|
-
text.setText(` ${editCount} edits ${s}${typeof diffLineCount === "number" ? ` ${theme.fg("muted", `(${diffLineCount} diff lines)`)}` : ""}`);
|
|
1829
|
+
setToolHeaderText(text, meta, theme);
|
|
1631
1830
|
return text;
|
|
1632
1831
|
}
|
|
1633
1832
|
text.__piDiffTask = undefined;
|