@heyhuynhgiabuu/pi-diff 0.6.0 → 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 +236 -116
- 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
|
@@ -24,22 +24,22 @@ 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
|
-
import { replace } from "./core/replace.js";
|
|
32
32
|
import { applyDiffPalette as applySharedDiffPalette, lang as detectDiffLanguage, renderSplit as renderSharedSplit, resolveDiffColors as resolveSharedDiffColors, themeCacheKey as sharedThemeCacheKey, } from "./review/hunk-preview.js";
|
|
33
33
|
const DIFF_PRESETS = {
|
|
34
34
|
default: {
|
|
35
35
|
name: "default",
|
|
36
36
|
description: "Original pi-diff colors — tuned for dark theme bases (~#1e1e2e)",
|
|
37
|
-
bgAdd: "#
|
|
38
|
-
bgDel: "#
|
|
39
|
-
bgAddHighlight: "#
|
|
40
|
-
bgDelHighlight: "#
|
|
41
|
-
bgGutterAdd: "#
|
|
42
|
-
bgGutterDel: "#
|
|
37
|
+
bgAdd: "#1a3324",
|
|
38
|
+
bgDel: "#3d2020",
|
|
39
|
+
bgAddHighlight: "#2d5c3a",
|
|
40
|
+
bgDelHighlight: "#5c2d2d",
|
|
41
|
+
bgGutterAdd: "#16281e",
|
|
42
|
+
bgGutterDel: "#301c1c",
|
|
43
43
|
bgEmpty: "#121212",
|
|
44
44
|
fgDim: "#505050",
|
|
45
45
|
fgLnum: "#646464",
|
|
@@ -50,12 +50,12 @@ const DIFF_PRESETS = {
|
|
|
50
50
|
midnight: {
|
|
51
51
|
name: "midnight",
|
|
52
52
|
description: "Subtle tints for pure black (#000000) terminal backgrounds",
|
|
53
|
-
bgAdd: "#
|
|
54
|
-
bgDel: "#
|
|
53
|
+
bgAdd: "#101e15",
|
|
54
|
+
bgDel: "#1e1010",
|
|
55
55
|
bgAddHighlight: "#1a3825",
|
|
56
56
|
bgDelHighlight: "#381a1a",
|
|
57
|
-
bgGutterAdd: "#
|
|
58
|
-
bgGutterDel: "#
|
|
57
|
+
bgGutterAdd: "#0c180c",
|
|
58
|
+
bgGutterDel: "#180c0c",
|
|
59
59
|
bgEmpty: "#080808",
|
|
60
60
|
fgDim: "#404040",
|
|
61
61
|
fgLnum: "#505050",
|
|
@@ -66,12 +66,12 @@ const DIFF_PRESETS = {
|
|
|
66
66
|
subtle: {
|
|
67
67
|
name: "subtle",
|
|
68
68
|
description: "Minimal backgrounds — barely-there tints for a clean look",
|
|
69
|
-
bgAdd: "#
|
|
70
|
-
bgDel: "#
|
|
69
|
+
bgAdd: "#0c160c",
|
|
70
|
+
bgDel: "#160c0c",
|
|
71
71
|
bgAddHighlight: "#122818",
|
|
72
72
|
bgDelHighlight: "#281212",
|
|
73
|
-
bgGutterAdd: "#
|
|
74
|
-
bgGutterDel: "#
|
|
73
|
+
bgGutterAdd: "#080e08",
|
|
74
|
+
bgGutterDel: "#0e0808",
|
|
75
75
|
bgEmpty: "#060606",
|
|
76
76
|
fgDim: "#383838",
|
|
77
77
|
fgLnum: "#484848",
|
|
@@ -82,12 +82,12 @@ const DIFF_PRESETS = {
|
|
|
82
82
|
neon: {
|
|
83
83
|
name: "neon",
|
|
84
84
|
description: "Higher contrast backgrounds for better visibility",
|
|
85
|
-
bgAdd: "#
|
|
86
|
-
bgDel: "#
|
|
85
|
+
bgAdd: "#1e3a28",
|
|
86
|
+
bgDel: "#3a1c18",
|
|
87
87
|
bgAddHighlight: "#2d5c3a",
|
|
88
88
|
bgDelHighlight: "#5c2d2d",
|
|
89
|
-
bgGutterAdd: "#
|
|
90
|
-
bgGutterDel: "#
|
|
89
|
+
bgGutterAdd: "#182c1c",
|
|
90
|
+
bgGutterDel: "#2c1410",
|
|
91
91
|
bgEmpty: "#141414",
|
|
92
92
|
fgDim: "#606060",
|
|
93
93
|
fgLnum: "#787878",
|
|
@@ -182,15 +182,15 @@ function autoDeriveBgFromTheme(theme) {
|
|
|
182
182
|
/* no toolErrorBg — use toolSuccessBg/black */
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
|
-
// Line backgrounds —
|
|
186
|
-
BG_ADD = mixBg(addBase, addRgb, 0.
|
|
187
|
-
BG_DEL = mixBg(delBase, delRgb, 0.
|
|
188
|
-
// Word-level highlights — more
|
|
189
|
-
BG_ADD_W = mixBg(addBase, addRgb, 0.
|
|
190
|
-
BG_DEL_W = mixBg(delBase, delRgb, 0.
|
|
191
|
-
// Gutters — subtler than lines (
|
|
192
|
-
BG_GUTTER_ADD = mixBg(addBase, addRgb, 0.
|
|
193
|
-
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);
|
|
194
194
|
// Empty filler and context — match the success/context base
|
|
195
195
|
BG_EMPTY = BG_BASE;
|
|
196
196
|
// Update RST to re-apply base bg after every reset — prevents black
|
|
@@ -205,7 +205,10 @@ function autoDeriveBgFromTheme(theme) {
|
|
|
205
205
|
}
|
|
206
206
|
/** Load diff theme config from .pi/settings.json (project-level, then global). */
|
|
207
207
|
function loadDiffConfig() {
|
|
208
|
-
const paths = [
|
|
208
|
+
const paths = [
|
|
209
|
+
`${process.cwd()}/.pi/settings.json`,
|
|
210
|
+
`${process.env.HOME ?? ""}/.pi/settings.json`,
|
|
211
|
+
];
|
|
209
212
|
for (const p of paths) {
|
|
210
213
|
try {
|
|
211
214
|
if (existsSync(p)) {
|
|
@@ -363,16 +366,15 @@ const MAX_WRAP_ROWS_NARROW = 1; // <120 cols (truncate, no wrap)
|
|
|
363
366
|
// ANSI
|
|
364
367
|
// ---------------------------------------------------------------------------
|
|
365
368
|
let RST = "\x1b[0m";
|
|
366
|
-
const BOLD = "\x1b[1m";
|
|
367
369
|
const DIM = "\x1b[2m";
|
|
368
370
|
// Subtle diff backgrounds — muted tones to let syntax fg shine through
|
|
369
371
|
// Override via env: DIFF_BG_ADD="#1a3320" etc. (hex "#RRGGBB" format)
|
|
370
|
-
let BG_ADD = envBg("DIFF_BG_ADD", "\x1b[48;2;
|
|
371
|
-
let BG_DEL = envBg("DIFF_BG_DEL", "\x1b[48;2;
|
|
372
|
-
let BG_ADD_W = envBg("DIFF_BG_ADD_HL", "\x1b[48;2;
|
|
373
|
-
let BG_DEL_W = envBg("DIFF_BG_DEL_HL", "\x1b[48;2;
|
|
374
|
-
let BG_GUTTER_ADD = envBg("DIFF_BG_GUTTER_ADD", "\x1b[48;2;
|
|
375
|
-
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");
|
|
376
378
|
const BG_GUTTER_CTX = ""; // use terminal default bg for context gutters
|
|
377
379
|
let BG_EMPTY = "\x1b[48;2;18;18;18m"; // filler rows when one side is shorter
|
|
378
380
|
// Diff foregrounds — override via env: DIFF_FG_ADD="#50d264" etc.
|
|
@@ -676,7 +678,7 @@ function shouldUseSplit(diff, tw, maxRows = MAX_PREVIEW_LINES) {
|
|
|
676
678
|
return false;
|
|
677
679
|
const nw = Math.max(2, String(Math.max(...diff.lines.map((l) => l.oldNum ?? l.newNum ?? 0), 0)).length);
|
|
678
680
|
const half = Math.floor((tw - 1) / 2); // -1 for center divider
|
|
679
|
-
const gw = nw +
|
|
681
|
+
const gw = nw + 4; // border + num + spaces around sign
|
|
680
682
|
const cw = Math.max(12, half - gw);
|
|
681
683
|
if (cw < SPLIT_MIN_CODE_WIDTH)
|
|
682
684
|
return false;
|
|
@@ -828,8 +830,6 @@ function wordDiffAnalysis(a, b) {
|
|
|
828
830
|
* Uses sorted-range pointer scan instead of Set (avoids O(totalChars) Set creation).
|
|
829
831
|
*/
|
|
830
832
|
function injectBg(ansiLine, ranges, baseBg, hlBg) {
|
|
831
|
-
if (!ranges.length)
|
|
832
|
-
return baseBg + ansiLine + RST;
|
|
833
833
|
let out = baseBg;
|
|
834
834
|
let vis = 0;
|
|
835
835
|
let inHL = false;
|
|
@@ -898,7 +898,7 @@ async function renderUnified(diff, language, max = MAX_RENDER_LINES, dc = DEFAUL
|
|
|
898
898
|
const vis = diff.lines.slice(0, max);
|
|
899
899
|
const tw = termW();
|
|
900
900
|
const nw = Math.max(2, String(Math.max(...vis.map((l) => l.oldNum ?? l.newNum ?? 0), 0)).length);
|
|
901
|
-
const gw = nw +
|
|
901
|
+
const gw = nw + 4; // border + num + spaces around sign
|
|
902
902
|
const cw = Math.max(20, tw - gw);
|
|
903
903
|
const canHL = diff.chars <= MAX_HL_CHARS && vis.length <= MAX_RENDER_LINES;
|
|
904
904
|
// Build separate old/new code blocks for highlighting
|
|
@@ -910,18 +910,20 @@ async function renderUnified(diff, language, max = MAX_RENDER_LINES, dc = DEFAUL
|
|
|
910
910
|
newSrc.push(l.content);
|
|
911
911
|
}
|
|
912
912
|
const [oldHL, newHL] = canHL
|
|
913
|
-
? await Promise.all([
|
|
913
|
+
? await Promise.all([
|
|
914
|
+
hlBlock(oldSrc.join("\n"), language),
|
|
915
|
+
hlBlock(newSrc.join("\n"), language),
|
|
916
|
+
])
|
|
914
917
|
: [oldSrc, newSrc];
|
|
915
918
|
let oI = 0, nI = 0, idx = 0;
|
|
916
919
|
const out = [];
|
|
917
|
-
out.push(rule(tw));
|
|
918
920
|
/** Emit a single stacked row with compact gutter + left border bar. */
|
|
919
921
|
function emitRow(num, sign, gutterBg, signFg, body, bodyBg = "") {
|
|
920
922
|
const borderFg = sign === "-" ? dc.fgDel : sign === "+" ? dc.fgAdd : "";
|
|
921
923
|
const border = borderFg ? `${borderFg}${getBorderBar()}${RST}` : `${BG_BASE} `;
|
|
922
924
|
const numFg = borderFg || FG_LNUM;
|
|
923
|
-
const gutter = `${border}${gutterBg}${lnum(num, nw, numFg)}${signFg}${sign}${
|
|
924
|
-
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}`;
|
|
925
927
|
const rows = wrapAnsi(tabs(body), cw, adaptiveWrapRows(), bodyBg);
|
|
926
928
|
out.push(`${gutter}${rows[0]}${RST}`);
|
|
927
929
|
for (let r = 1; r < rows.length; r++)
|
|
@@ -972,27 +974,26 @@ async function renderUnified(diff, language, max = MAX_RENDER_LINES, dc = DEFAUL
|
|
|
972
974
|
if (isPaired && wdBalanced && wd.similarity >= WORD_DIFF_MIN_SIM && canHL) {
|
|
973
975
|
const delBody = injectBg(dels[0].hl, wd.oldRanges, BG_DEL, BG_DEL_W);
|
|
974
976
|
const addBody = injectBg(adds[0].hl, wd.newRanges, BG_ADD, BG_ADD_W);
|
|
975
|
-
emitRow(dels[0].l.oldNum, "-", BG_GUTTER_DEL,
|
|
976
|
-
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);
|
|
977
979
|
continue;
|
|
978
980
|
}
|
|
979
981
|
if (isPaired && wdBalanced && wd.similarity >= WORD_DIFF_MIN_SIM && !canHL) {
|
|
980
982
|
const pwd = plainWordDiff(dels[0].l.content, adds[0].l.content);
|
|
981
|
-
emitRow(dels[0].l.oldNum, "-", BG_GUTTER_DEL,
|
|
982
|
-
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);
|
|
983
985
|
continue;
|
|
984
986
|
}
|
|
985
987
|
// Multi-line blocks — syntax highlighted with diff bg
|
|
986
988
|
for (const d of dels) {
|
|
987
|
-
const body = canHL ?
|
|
988
|
-
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);
|
|
989
991
|
}
|
|
990
992
|
for (const a of adds) {
|
|
991
|
-
const body = canHL ?
|
|
992
|
-
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);
|
|
993
995
|
}
|
|
994
996
|
}
|
|
995
|
-
out.push(rule(tw));
|
|
996
997
|
if (diff.lines.length > vis.length) {
|
|
997
998
|
out.push(`${BG_BASE}${FG_DIM} … ${diff.lines.length - vis.length} more lines${RST}`);
|
|
998
999
|
}
|
|
@@ -1039,7 +1040,7 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1039
1040
|
const vis = rows.slice(0, max);
|
|
1040
1041
|
const half = Math.floor((tw - 1) / 2); // -1 for center divider
|
|
1041
1042
|
const nw = Math.max(2, String(Math.max(...diff.lines.map((l) => l.oldNum ?? l.newNum ?? 0), 0)).length);
|
|
1042
|
-
const gw = nw +
|
|
1043
|
+
const gw = nw + 4; // border + num + spaces around sign
|
|
1043
1044
|
const cw = Math.max(12, half - gw);
|
|
1044
1045
|
const canHL = diff.chars <= MAX_HL_CHARS && vis.length * 2 <= MAX_RENDER_LINES * 2;
|
|
1045
1046
|
// Build separate code blocks per side
|
|
@@ -1051,22 +1052,25 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1051
1052
|
rightSrc.push(r.right.content);
|
|
1052
1053
|
}
|
|
1053
1054
|
const [leftHL, rightHL] = canHL
|
|
1054
|
-
? await Promise.all([
|
|
1055
|
+
? await Promise.all([
|
|
1056
|
+
hlBlock(leftSrc.join("\n"), language),
|
|
1057
|
+
hlBlock(rightSrc.join("\n"), language),
|
|
1058
|
+
])
|
|
1055
1059
|
: [leftSrc, rightSrc];
|
|
1056
1060
|
let lI = 0, rI = 0;
|
|
1057
1061
|
let stripeRow = 0; // tracks row index for diagonal stripe offset
|
|
1058
1062
|
function half_build(line, hl, ranges, side) {
|
|
1059
1063
|
// Empty filler — diagonal stripes
|
|
1060
1064
|
if (!line) {
|
|
1061
|
-
const gw2 = nw +
|
|
1065
|
+
const gw2 = nw + 4; // border + number + spaces around sign
|
|
1062
1066
|
const gPat = FG_STRIPE + "╱".repeat(gw2) + RST;
|
|
1063
|
-
const g =
|
|
1067
|
+
const g = gPat;
|
|
1064
1068
|
return { gutter: g, contGutter: g, bodyRows: [stripes(cw, stripeRow)] };
|
|
1065
1069
|
}
|
|
1066
1070
|
// Hunk separator with optional function context
|
|
1067
1071
|
if (line.type === "sep") {
|
|
1068
1072
|
const label = sepLabelSplit(getSepStyle(), line.hunkMeta, line.newNum, line.content);
|
|
1069
|
-
const g = `${BG_BASE} ${FG_DIM}${fit("", nw +
|
|
1073
|
+
const g = `${BG_BASE} ${FG_DIM}${fit("", nw + 3)}${RST}`;
|
|
1070
1074
|
return { gutter: g, contGutter: g, bodyRows: [`${BG_BASE}${FG_DIM}${fit(label, cw)}${RST}`] };
|
|
1071
1075
|
}
|
|
1072
1076
|
const isDel = line.type === "del", isAdd = line.type === "add";
|
|
@@ -1074,7 +1078,13 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1074
1078
|
const cBg = isDel ? BG_DEL : isAdd ? BG_ADD : BG_BASE;
|
|
1075
1079
|
const sFg = isDel ? dc.fgDel : isAdd ? dc.fgAdd : dc.fgCtx;
|
|
1076
1080
|
const sign = isDel ? "-" : isAdd ? "+" : " ";
|
|
1077
|
-
const num = isDel
|
|
1081
|
+
const num = isDel
|
|
1082
|
+
? line.oldNum
|
|
1083
|
+
: isAdd
|
|
1084
|
+
? line.newNum
|
|
1085
|
+
: side === "left"
|
|
1086
|
+
? line.oldNum
|
|
1087
|
+
: line.newNum;
|
|
1078
1088
|
// Border bar + colored line numbers for changed lines
|
|
1079
1089
|
const borderFg = isDel ? dc.fgDel : isAdd ? dc.fgAdd : "";
|
|
1080
1090
|
const border = borderFg ? `${borderFg}${getBorderBar()}${RST}` : ` ${BG_BASE}`;
|
|
@@ -1084,13 +1094,13 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1084
1094
|
body = injectBg(hl, ranges, cBg, isDel ? BG_DEL_W : BG_ADD_W);
|
|
1085
1095
|
}
|
|
1086
1096
|
else if (isDel || isAdd) {
|
|
1087
|
-
body =
|
|
1097
|
+
body = injectBg(hl, [], cBg, cBg);
|
|
1088
1098
|
}
|
|
1089
1099
|
else {
|
|
1090
1100
|
body = `${BG_BASE}${DIM}${hl}`;
|
|
1091
1101
|
}
|
|
1092
|
-
const gutter = `${border}${gBg}${lnum(num, nw, numFg)}${
|
|
1093
|
-
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}`;
|
|
1094
1104
|
const bodyRows = wrapAnsi(tabs(body), cw, adaptiveWrapRows(), cBg);
|
|
1095
1105
|
return { gutter, contGutter, bodyRows };
|
|
1096
1106
|
}
|
|
@@ -1099,7 +1109,6 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1099
1109
|
const hdrOld = `${BG_BASE}${" ".repeat(Math.max(0, nw - 2))}${dc.fgDel}${DIM}old${RST}`;
|
|
1100
1110
|
const hdrNew = `${BG_BASE}${" ".repeat(Math.max(0, nw - 2))}${dc.fgAdd}${DIM}new${RST}`;
|
|
1101
1111
|
out.push(`${BG_BASE}${hdrOld}${" ".repeat(Math.max(0, half - nw - 1))}${FG_RULE}┊${RST}${hdrNew}`);
|
|
1102
|
-
out.push(`${rule(half)}${FG_RULE}┊${RST}${rule(half)}`);
|
|
1103
1112
|
for (const r of vis) {
|
|
1104
1113
|
const leftLine = r.left, rightLine = r.right;
|
|
1105
1114
|
const paired = leftLine && rightLine && leftLine.type === "del" && rightLine.type === "add";
|
|
@@ -1131,13 +1140,14 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1131
1140
|
for (let row = 0; row < maxRows; row++) {
|
|
1132
1141
|
const lg = row === 0 ? lResult.gutter : lResult.contGutter;
|
|
1133
1142
|
const rg = row === 0 ? rResult.gutter : rResult.contGutter;
|
|
1134
|
-
const lb = lResult.bodyRows[row] ??
|
|
1135
|
-
|
|
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}`);
|
|
1136
1147
|
out.push(`${lg}${lb}${DIVIDER}${rg}${rb}`);
|
|
1137
1148
|
stripeRow++;
|
|
1138
1149
|
}
|
|
1139
1150
|
}
|
|
1140
|
-
out.push(`${rule(half)}${FG_RULE}┊${RST}${rule(half)}`);
|
|
1141
1151
|
if (rows.length > vis.length) {
|
|
1142
1152
|
out.push(`${BG_BASE}${FG_DIM} … ${rows.length - vis.length} more lines${RST}`);
|
|
1143
1153
|
}
|
|
@@ -1180,6 +1190,70 @@ export default async function diffRendererExtension(pi) {
|
|
|
1180
1190
|
const cwd = process.cwd();
|
|
1181
1191
|
const home = process.env.HOME ?? "";
|
|
1182
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
|
+
}
|
|
1183
1257
|
/** Wrap a Text component so its render(width) kicks off async diff rendering
|
|
1184
1258
|
* using the real TUI width (which accounts for the sidebar). */
|
|
1185
1259
|
function getWidthAwareText(lastComponent) {
|
|
@@ -1214,7 +1288,7 @@ export default async function diffRendererExtension(pi) {
|
|
|
1214
1288
|
});
|
|
1215
1289
|
}
|
|
1216
1290
|
}
|
|
1217
|
-
return text.__piDiffRender(width);
|
|
1291
|
+
return text.__piDiffRender?.(width) ?? [];
|
|
1218
1292
|
};
|
|
1219
1293
|
return text;
|
|
1220
1294
|
}
|
|
@@ -1263,7 +1337,11 @@ Examples:
|
|
|
1263
1337
|
try {
|
|
1264
1338
|
const { existingCode, filePath, patchText, fileContent } = params ?? {};
|
|
1265
1339
|
if (!existingCode || !filePath) {
|
|
1266
|
-
return {
|
|
1340
|
+
return {
|
|
1341
|
+
content: [
|
|
1342
|
+
{ type: "text", text: "Error: existingCode and filePath are required" },
|
|
1343
|
+
],
|
|
1344
|
+
};
|
|
1267
1345
|
}
|
|
1268
1346
|
// Fetch git diff if patchText not provided
|
|
1269
1347
|
let patch = patchText;
|
|
@@ -1289,25 +1367,42 @@ Examples:
|
|
|
1289
1367
|
}
|
|
1290
1368
|
catch {
|
|
1291
1369
|
return {
|
|
1292
|
-
content: [
|
|
1370
|
+
content: [
|
|
1371
|
+
{ type: "text", text: `Error: could not read git diff for ${filePath}` },
|
|
1372
|
+
],
|
|
1293
1373
|
};
|
|
1294
1374
|
}
|
|
1295
1375
|
}
|
|
1296
1376
|
if (!patch) {
|
|
1297
1377
|
return {
|
|
1298
|
-
content: [
|
|
1378
|
+
content: [
|
|
1379
|
+
{
|
|
1380
|
+
type: "text",
|
|
1381
|
+
text: `No diff found for ${filePath} — file may not have changes`,
|
|
1382
|
+
},
|
|
1383
|
+
],
|
|
1299
1384
|
details: { unresolved: true },
|
|
1300
1385
|
};
|
|
1301
1386
|
}
|
|
1302
1387
|
const result = resolveLinesFromPatch(existingCode, patch, fileContent);
|
|
1303
1388
|
if ("unresolved" in result) {
|
|
1304
1389
|
return {
|
|
1305
|
-
content: [
|
|
1390
|
+
content: [
|
|
1391
|
+
{
|
|
1392
|
+
type: "text",
|
|
1393
|
+
text: `Could not resolve line numbers for existing_code in ${filePath}`,
|
|
1394
|
+
},
|
|
1395
|
+
],
|
|
1306
1396
|
details: { unresolved: true },
|
|
1307
1397
|
};
|
|
1308
1398
|
}
|
|
1309
1399
|
return {
|
|
1310
|
-
content: [
|
|
1400
|
+
content: [
|
|
1401
|
+
{
|
|
1402
|
+
type: "text",
|
|
1403
|
+
text: `Resolved ${filePath}:${result.startLine}-${result.endLine}`,
|
|
1404
|
+
},
|
|
1405
|
+
],
|
|
1311
1406
|
details: { startLine: result.startLine, endLine: result.endLine },
|
|
1312
1407
|
};
|
|
1313
1408
|
}
|
|
@@ -1367,11 +1462,24 @@ Examples:
|
|
|
1367
1462
|
const useFull = !!params._expandGaps;
|
|
1368
1463
|
const diff = parseDiff(old, content, useFull ? undefined : 3);
|
|
1369
1464
|
const lg = detectDiffLanguage(fp);
|
|
1370
|
-
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
|
+
};
|
|
1371
1474
|
}
|
|
1372
1475
|
else if (old === null) {
|
|
1373
1476
|
const lineCount = content ? content.split("\n").length : 0;
|
|
1374
|
-
result.details = {
|
|
1477
|
+
result.details = {
|
|
1478
|
+
_type: "new",
|
|
1479
|
+
lines: lineCount,
|
|
1480
|
+
content: content ?? "",
|
|
1481
|
+
filePath: fp,
|
|
1482
|
+
};
|
|
1375
1483
|
}
|
|
1376
1484
|
else if (old === content) {
|
|
1377
1485
|
result.details = { _type: "noChange" };
|
|
@@ -1431,15 +1539,7 @@ Examples:
|
|
|
1431
1539
|
}
|
|
1432
1540
|
const d = result.details;
|
|
1433
1541
|
if (d?._type === "diff") {
|
|
1434
|
-
|
|
1435
|
-
const dc = resolveSharedDiffColors(theme);
|
|
1436
|
-
text.__piDiffTask = {
|
|
1437
|
-
placeholder: ` ${d.summary}\n${theme.fg("muted", " rendering diff…")}`,
|
|
1438
|
-
fallback: ` ${d.summary}`,
|
|
1439
|
-
invalidate: ctx.invalidate,
|
|
1440
|
-
key: (width) => `wd:${themeKey}:${width}:${d.summary}:${d.diff?.lines?.length ?? 0}:${d.language ?? ""}`,
|
|
1441
|
-
render: async (width) => ` ${d.summary}\n${await renderSharedSplit(d.diff, d.language, MAX_RENDER_LINES, dc, width)}`,
|
|
1442
|
-
};
|
|
1542
|
+
setDiffPreviewTask(text, "wd", d.summary, d.diff, d.language, MAX_RENDER_LINES, theme, ctx);
|
|
1443
1543
|
return text;
|
|
1444
1544
|
}
|
|
1445
1545
|
if (d?._type === "noChange") {
|
|
@@ -1486,13 +1586,29 @@ Examples:
|
|
|
1486
1586
|
if (Array.isArray(input?.edits)) {
|
|
1487
1587
|
return input.edits
|
|
1488
1588
|
.map((edit) => ({
|
|
1489
|
-
oldText: typeof edit?.oldText === "string"
|
|
1490
|
-
|
|
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
|
+
: "",
|
|
1491
1599
|
}))
|
|
1492
1600
|
.filter((edit) => edit.oldText && edit.oldText !== edit.newText);
|
|
1493
1601
|
}
|
|
1494
|
-
const oldText = typeof input?.oldText === "string"
|
|
1495
|
-
|
|
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
|
+
: "";
|
|
1496
1612
|
return oldText && oldText !== newText ? [{ oldText, newText }] : [];
|
|
1497
1613
|
}
|
|
1498
1614
|
function summarizeEditOperations(operations) {
|
|
@@ -1553,6 +1669,7 @@ Examples:
|
|
|
1553
1669
|
details: {
|
|
1554
1670
|
_type: "editInfo",
|
|
1555
1671
|
summary,
|
|
1672
|
+
filePath: fp,
|
|
1556
1673
|
editLine,
|
|
1557
1674
|
diff: diffData,
|
|
1558
1675
|
language: lg,
|
|
@@ -1565,7 +1682,16 @@ Examples:
|
|
|
1565
1682
|
// Merge all diffs into one combined view for rendering
|
|
1566
1683
|
const merged = {
|
|
1567
1684
|
lines: diffs.flatMap((diff, i) => [
|
|
1568
|
-
...(i > 0
|
|
1685
|
+
...(i > 0
|
|
1686
|
+
? [
|
|
1687
|
+
{
|
|
1688
|
+
type: "sep",
|
|
1689
|
+
oldNum: null,
|
|
1690
|
+
newNum: null,
|
|
1691
|
+
content: `───── Edit ${i + 1} ─────`,
|
|
1692
|
+
},
|
|
1693
|
+
]
|
|
1694
|
+
: []),
|
|
1569
1695
|
...diff.lines,
|
|
1570
1696
|
]),
|
|
1571
1697
|
added: diffs.reduce((sum, diff) => sum + diff.added, 0),
|
|
@@ -1577,6 +1703,7 @@ Examples:
|
|
|
1577
1703
|
details: {
|
|
1578
1704
|
_type: "multiEditInfo",
|
|
1579
1705
|
summary,
|
|
1706
|
+
filePath: fp,
|
|
1580
1707
|
editCount: operations.length,
|
|
1581
1708
|
diffLineCount: merged.lines.length,
|
|
1582
1709
|
diff: merged,
|
|
@@ -1615,6 +1742,7 @@ Examples:
|
|
|
1615
1742
|
result.details = {
|
|
1616
1743
|
_type: "editInfo",
|
|
1617
1744
|
summary,
|
|
1745
|
+
filePath: fp,
|
|
1618
1746
|
editLine,
|
|
1619
1747
|
diff: diffData,
|
|
1620
1748
|
language: lg,
|
|
@@ -1627,7 +1755,16 @@ Examples:
|
|
|
1627
1755
|
const merged = {
|
|
1628
1756
|
lines: diffs.flatMap((diff, i) => [
|
|
1629
1757
|
// Add separator between multiple edits
|
|
1630
|
-
...(i > 0
|
|
1758
|
+
...(i > 0
|
|
1759
|
+
? [
|
|
1760
|
+
{
|
|
1761
|
+
type: "sep",
|
|
1762
|
+
oldNum: null,
|
|
1763
|
+
newNum: null,
|
|
1764
|
+
content: `───── Edit ${i + 1} ─────`,
|
|
1765
|
+
},
|
|
1766
|
+
]
|
|
1767
|
+
: []),
|
|
1631
1768
|
...diff.lines,
|
|
1632
1769
|
]),
|
|
1633
1770
|
added: diffs.reduce((sum, diff) => sum + diff.added, 0),
|
|
@@ -1637,6 +1774,7 @@ Examples:
|
|
|
1637
1774
|
result.details = {
|
|
1638
1775
|
_type: "multiEditInfo",
|
|
1639
1776
|
summary,
|
|
1777
|
+
filePath: fp,
|
|
1640
1778
|
editCount: operations.length,
|
|
1641
1779
|
diffLineCount: merged.lines.length,
|
|
1642
1780
|
diff: merged,
|
|
@@ -1648,13 +1786,13 @@ Examples:
|
|
|
1648
1786
|
const fp = args?.path ?? args?.file_path ?? "";
|
|
1649
1787
|
const operations = getEditOperations(args);
|
|
1650
1788
|
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
1651
|
-
|
|
1789
|
+
resolvePreviewDiffColors(theme);
|
|
1652
1790
|
if (ctx.argsComplete && operations.length > 0) {
|
|
1653
1791
|
const { totalAdded, totalRemoved } = summarizeEditOperations(operations);
|
|
1654
|
-
text.setText(
|
|
1792
|
+
text.setText(formatToolTitle("edit", fp, theme, termW(), ` ${theme.fg("muted", summarize(totalAdded, totalRemoved))}`));
|
|
1655
1793
|
}
|
|
1656
1794
|
else {
|
|
1657
|
-
text.setText(
|
|
1795
|
+
text.setText(formatToolTitle("edit", fp, theme, termW()));
|
|
1658
1796
|
}
|
|
1659
1797
|
return text;
|
|
1660
1798
|
},
|
|
@@ -1671,42 +1809,24 @@ Examples:
|
|
|
1671
1809
|
}
|
|
1672
1810
|
const d = result.details;
|
|
1673
1811
|
if (d?._type === "editInfo" && d.diff) {
|
|
1674
|
-
const themeKey = sharedThemeCacheKey(theme);
|
|
1675
|
-
const dc = resolveSharedDiffColors(theme);
|
|
1676
1812
|
const loc = d.editLine > 0 ? ` ${theme.fg("muted", `at line ${d.editLine}`)}` : "";
|
|
1677
|
-
text.
|
|
1678
|
-
placeholder: ` ${d.summary}${loc}\n${theme.fg("muted", " rendering diff…")}`,
|
|
1679
|
-
fallback: ` ${d.summary}${loc}`,
|
|
1680
|
-
invalidate: ctx.invalidate,
|
|
1681
|
-
key: (width) => `ed:${themeKey}:${width}:${d.summary}:${d.diff?.lines?.length ?? 0}:${d.language ?? ""}`,
|
|
1682
|
-
render: async (width) => ` ${d.summary}${loc}\n${await renderSharedSplit(d.diff, d.language, MAX_PREVIEW_LINES, dc, width)}`,
|
|
1683
|
-
};
|
|
1813
|
+
setDiffPreviewTask(text, "ed", `${d.summary}${loc}`, d.diff, d.language, MAX_PREVIEW_LINES, theme, ctx);
|
|
1684
1814
|
return text;
|
|
1685
1815
|
}
|
|
1686
1816
|
if (d?._type === "editInfo") {
|
|
1687
1817
|
const { summary: s, editLine } = d;
|
|
1688
1818
|
const loc = editLine > 0 ? ` ${theme.fg("muted", `at line ${editLine}`)}` : "";
|
|
1689
|
-
text
|
|
1690
|
-
text.setText(` ${s}${loc}`);
|
|
1819
|
+
setToolHeaderText(text, `${s}${loc}`, theme);
|
|
1691
1820
|
return text;
|
|
1692
1821
|
}
|
|
1693
1822
|
if (d?._type === "multiEditInfo") {
|
|
1694
1823
|
const { summary: s, editCount, diffLineCount, diff, language } = d;
|
|
1824
|
+
const meta = `${editCount} edits ${s}${diffLineCountLabel(diffLineCount, theme)}`;
|
|
1695
1825
|
if (diff) {
|
|
1696
|
-
|
|
1697
|
-
const dc = resolveSharedDiffColors(theme);
|
|
1698
|
-
text.__piDiffTask = {
|
|
1699
|
-
placeholder: ` ${editCount} edits ${s}
|
|
1700
|
-
${theme.fg("muted", " rendering diff…")}`,
|
|
1701
|
-
fallback: ` ${editCount} edits ${s}${typeof diffLineCount === "number" ? ` ${theme.fg("muted", `(${diffLineCount} diff lines)`)}` : ""}`,
|
|
1702
|
-
invalidate: ctx.invalidate,
|
|
1703
|
-
key: (width) => `me:${themeKey}:${width}:${s}:${diff?.lines?.length ?? 0}:${language ?? ""}`,
|
|
1704
|
-
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)}`,
|
|
1705
|
-
};
|
|
1826
|
+
setDiffPreviewTask(text, "me", meta, diff, language, MAX_PREVIEW_LINES, theme, ctx);
|
|
1706
1827
|
return text;
|
|
1707
1828
|
}
|
|
1708
|
-
text
|
|
1709
|
-
text.setText(` ${editCount} edits ${s}${typeof diffLineCount === "number" ? ` ${theme.fg("muted", `(${diffLineCount} diff lines)`)}` : ""}`);
|
|
1829
|
+
setToolHeaderText(text, meta, theme);
|
|
1710
1830
|
return text;
|
|
1711
1831
|
}
|
|
1712
1832
|
text.__piDiffTask = undefined;
|