@heyhuynhgiabuu/pi-diff 0.6.0 → 0.6.2
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/diff.d.ts +2 -2
- package/dist/core/diff.d.ts.map +1 -1
- package/dist/core/diff.js +29 -31
- package/dist/core/diff.js.map +1 -1
- 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 +262 -134
- 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 +94 -74
- 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,22 +182,22 @@ 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
|
|
197
197
|
// flashes between styled segments when toolSuccessBg is non-black
|
|
198
198
|
RST = `\x1b[0m${BG_BASE}`;
|
|
199
199
|
// Rebuild derived constants
|
|
200
|
-
DIVIDER = `${FG_RULE}
|
|
200
|
+
DIVIDER = `${FG_RULE}${RST}`;
|
|
201
201
|
}
|
|
202
202
|
catch {
|
|
203
203
|
// Fall back to defaults silently
|
|
@@ -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)) {
|
|
@@ -304,7 +307,7 @@ function applyDiffPalette() {
|
|
|
304
307
|
if (shiki)
|
|
305
308
|
THEME = shiki;
|
|
306
309
|
// --- Rebuild derived constants ---
|
|
307
|
-
DIVIDER = `${FG_RULE}
|
|
310
|
+
DIVIDER = `${FG_RULE}${RST}`;
|
|
308
311
|
DEFAULT_DIFF_COLORS = { fgAdd: FG_ADD, fgDel: FG_DEL, fgCtx: FG_DIM };
|
|
309
312
|
// If no explicit bg config, auto-derive will run on first render
|
|
310
313
|
_autoDerivePending = !_hasExplicitBgConfig;
|
|
@@ -339,10 +342,10 @@ function envBg(name, fallback) {
|
|
|
339
342
|
// --- Split-view thresholds ---
|
|
340
343
|
// Split is preferred when there's real room. At narrow widths, a clean stacked
|
|
341
344
|
// (unified) view is better than a cramped split with wrapping.
|
|
342
|
-
const SPLIT_MIN_WIDTH = envInt("DIFF_SPLIT_MIN_WIDTH",
|
|
343
|
-
const SPLIT_MIN_CODE_WIDTH = envInt("DIFF_SPLIT_MIN_CODE_WIDTH",
|
|
344
|
-
const SPLIT_MAX_WRAP_RATIO = 0.
|
|
345
|
-
const SPLIT_MAX_WRAP_LINES =
|
|
345
|
+
const SPLIT_MIN_WIDTH = envInt("DIFF_SPLIT_MIN_WIDTH", 80); // allow split in normal terminals
|
|
346
|
+
const SPLIT_MIN_CODE_WIDTH = envInt("DIFF_SPLIT_MIN_CODE_WIDTH", 24); // short balanced hunks can split
|
|
347
|
+
const SPLIT_MAX_WRAP_RATIO = 0.35; // wrap-heavy hunks fall back to unified
|
|
348
|
+
const SPLIT_MAX_WRAP_LINES = 10; // absolute cap before unified fallback
|
|
346
349
|
// --- Terminal bounds ---
|
|
347
350
|
const MAX_TERM_WIDTH = 210; // max for 1728px wide display (~205 cols at typical font)
|
|
348
351
|
const DEFAULT_TERM_WIDTH = 200; // safe default for 1728x1117 resolution
|
|
@@ -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.
|
|
@@ -392,7 +394,7 @@ function getBorderBar() {
|
|
|
392
394
|
function stripes(w, _rowOffset) {
|
|
393
395
|
return BG_BASE + FG_STRIPE + "╱".repeat(w) + RST;
|
|
394
396
|
}
|
|
395
|
-
let DIVIDER = `${FG_RULE}
|
|
397
|
+
let DIVIDER = `${FG_RULE}${RST}`;
|
|
396
398
|
const ESC_RE = "\u001b";
|
|
397
399
|
const ANSI_RE = new RegExp(`${ESC_RE}\\[[0-9;]*m`, "g");
|
|
398
400
|
const ANSI_CAPTURE_RE = new RegExp(`${ESC_RE}\\[([^m]*)m`, "g");
|
|
@@ -675,13 +677,20 @@ function shouldUseSplit(diff, tw, maxRows = MAX_PREVIEW_LINES) {
|
|
|
675
677
|
if (tw < SPLIT_MIN_WIDTH)
|
|
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
|
-
const half = Math.floor(
|
|
679
|
-
const gw = nw +
|
|
680
|
+
const half = Math.floor(tw / 2);
|
|
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;
|
|
683
|
-
//
|
|
685
|
+
// Split view only helps balanced replacements. One-sided or heavily
|
|
686
|
+
// imbalanced hunks waste a column and should use unified rendering.
|
|
684
687
|
const vis = diff.lines.slice(0, maxRows);
|
|
688
|
+
const visibleAdd = vis.filter((line) => line.type === "add").length;
|
|
689
|
+
const visibleDel = vis.filter((line) => line.type === "del").length;
|
|
690
|
+
if (visibleAdd === 0 || visibleDel === 0)
|
|
691
|
+
return false;
|
|
692
|
+
if (Math.max(visibleAdd, visibleDel) > Math.min(visibleAdd, visibleDel) * 2)
|
|
693
|
+
return false;
|
|
685
694
|
let contentLines = 0;
|
|
686
695
|
let wrapCandidates = 0;
|
|
687
696
|
for (const l of vis) {
|
|
@@ -828,8 +837,6 @@ function wordDiffAnalysis(a, b) {
|
|
|
828
837
|
* Uses sorted-range pointer scan instead of Set (avoids O(totalChars) Set creation).
|
|
829
838
|
*/
|
|
830
839
|
function injectBg(ansiLine, ranges, baseBg, hlBg) {
|
|
831
|
-
if (!ranges.length)
|
|
832
|
-
return baseBg + ansiLine + RST;
|
|
833
840
|
let out = baseBg;
|
|
834
841
|
let vis = 0;
|
|
835
842
|
let inHL = false;
|
|
@@ -898,7 +905,7 @@ async function renderUnified(diff, language, max = MAX_RENDER_LINES, dc = DEFAUL
|
|
|
898
905
|
const vis = diff.lines.slice(0, max);
|
|
899
906
|
const tw = termW();
|
|
900
907
|
const nw = Math.max(2, String(Math.max(...vis.map((l) => l.oldNum ?? l.newNum ?? 0), 0)).length);
|
|
901
|
-
const gw = nw +
|
|
908
|
+
const gw = nw + 4; // border + num + spaces around sign
|
|
902
909
|
const cw = Math.max(20, tw - gw);
|
|
903
910
|
const canHL = diff.chars <= MAX_HL_CHARS && vis.length <= MAX_RENDER_LINES;
|
|
904
911
|
// Build separate old/new code blocks for highlighting
|
|
@@ -910,18 +917,20 @@ async function renderUnified(diff, language, max = MAX_RENDER_LINES, dc = DEFAUL
|
|
|
910
917
|
newSrc.push(l.content);
|
|
911
918
|
}
|
|
912
919
|
const [oldHL, newHL] = canHL
|
|
913
|
-
? await Promise.all([
|
|
920
|
+
? await Promise.all([
|
|
921
|
+
hlBlock(oldSrc.join("\n"), language),
|
|
922
|
+
hlBlock(newSrc.join("\n"), language),
|
|
923
|
+
])
|
|
914
924
|
: [oldSrc, newSrc];
|
|
915
925
|
let oI = 0, nI = 0, idx = 0;
|
|
916
926
|
const out = [];
|
|
917
|
-
out.push(rule(tw));
|
|
918
927
|
/** Emit a single stacked row with compact gutter + left border bar. */
|
|
919
928
|
function emitRow(num, sign, gutterBg, signFg, body, bodyBg = "") {
|
|
920
929
|
const borderFg = sign === "-" ? dc.fgDel : sign === "+" ? dc.fgAdd : "";
|
|
921
930
|
const border = borderFg ? `${borderFg}${getBorderBar()}${RST}` : `${BG_BASE} `;
|
|
922
931
|
const numFg = borderFg || FG_LNUM;
|
|
923
|
-
const gutter = `${border}${gutterBg}${lnum(num, nw, numFg)}${signFg}${sign}${
|
|
924
|
-
const contGutter = `${border}${gutterBg}${" ".repeat(nw +
|
|
932
|
+
const gutter = `${border}${gutterBg}${lnum(num, nw, numFg)}${gutterBg} ${signFg}${sign}${gutterBg} ${RST}`;
|
|
933
|
+
const contGutter = `${border}${gutterBg}${" ".repeat(nw + 3)}${RST}`;
|
|
925
934
|
const rows = wrapAnsi(tabs(body), cw, adaptiveWrapRows(), bodyBg);
|
|
926
935
|
out.push(`${gutter}${rows[0]}${RST}`);
|
|
927
936
|
for (let r = 1; r < rows.length; r++)
|
|
@@ -932,6 +941,10 @@ async function renderUnified(diff, language, max = MAX_RENDER_LINES, dc = DEFAUL
|
|
|
932
941
|
// Hunk separator — collapsed context with optional function context
|
|
933
942
|
if (l.type === "sep") {
|
|
934
943
|
const label = sepLabelUnified(getSepStyle(), l.hunkMeta, l.newNum, l.content);
|
|
944
|
+
if (!label) {
|
|
945
|
+
idx++;
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
935
948
|
const totalW = Math.min(tw, 72);
|
|
936
949
|
const pad = Math.max(0, totalW - label.length - 2);
|
|
937
950
|
const half1 = Math.floor(pad / 2), half2 = pad - half1;
|
|
@@ -972,27 +985,26 @@ async function renderUnified(diff, language, max = MAX_RENDER_LINES, dc = DEFAUL
|
|
|
972
985
|
if (isPaired && wdBalanced && wd.similarity >= WORD_DIFF_MIN_SIM && canHL) {
|
|
973
986
|
const delBody = injectBg(dels[0].hl, wd.oldRanges, BG_DEL, BG_DEL_W);
|
|
974
987
|
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,
|
|
988
|
+
emitRow(dels[0].l.oldNum, "-", BG_GUTTER_DEL, dc.fgDel, delBody, BG_DEL);
|
|
989
|
+
emitRow(adds[0].l.newNum, "+", BG_GUTTER_ADD, dc.fgAdd, addBody, BG_ADD);
|
|
977
990
|
continue;
|
|
978
991
|
}
|
|
979
992
|
if (isPaired && wdBalanced && wd.similarity >= WORD_DIFF_MIN_SIM && !canHL) {
|
|
980
993
|
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,
|
|
994
|
+
emitRow(dels[0].l.oldNum, "-", BG_GUTTER_DEL, dc.fgDel, `${BG_DEL}${pwd.old}`, BG_DEL);
|
|
995
|
+
emitRow(adds[0].l.newNum, "+", BG_GUTTER_ADD, dc.fgAdd, `${BG_ADD}${pwd.new}`, BG_ADD);
|
|
983
996
|
continue;
|
|
984
997
|
}
|
|
985
998
|
// Multi-line blocks — syntax highlighted with diff bg
|
|
986
999
|
for (const d of dels) {
|
|
987
|
-
const body = canHL ?
|
|
988
|
-
emitRow(d.l.oldNum, "-", BG_GUTTER_DEL,
|
|
1000
|
+
const body = canHL ? injectBg(d.hl, [], BG_DEL, BG_DEL) : `${BG_DEL}${d.l.content}`;
|
|
1001
|
+
emitRow(d.l.oldNum, "-", BG_GUTTER_DEL, dc.fgDel, body, BG_DEL);
|
|
989
1002
|
}
|
|
990
1003
|
for (const a of adds) {
|
|
991
|
-
const body = canHL ?
|
|
992
|
-
emitRow(a.l.newNum, "+", BG_GUTTER_ADD,
|
|
1004
|
+
const body = canHL ? injectBg(a.hl, [], BG_ADD, BG_ADD) : `${BG_ADD}${a.l.content}`;
|
|
1005
|
+
emitRow(a.l.newNum, "+", BG_GUTTER_ADD, dc.fgAdd, body, BG_ADD);
|
|
993
1006
|
}
|
|
994
1007
|
}
|
|
995
|
-
out.push(rule(tw));
|
|
996
1008
|
if (diff.lines.length > vis.length) {
|
|
997
1009
|
out.push(`${BG_BASE}${FG_DIM} … ${diff.lines.length - vis.length} more lines${RST}`);
|
|
998
1010
|
}
|
|
@@ -1037,9 +1049,9 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1037
1049
|
rows.push({ left: dels[j] ?? null, right: adds[j] ?? null });
|
|
1038
1050
|
}
|
|
1039
1051
|
const vis = rows.slice(0, max);
|
|
1040
|
-
const half = Math.floor((tw - 1) / 2); // -1 for center divider
|
|
1041
1052
|
const nw = Math.max(2, String(Math.max(...diff.lines.map((l) => l.oldNum ?? l.newNum ?? 0), 0)).length);
|
|
1042
|
-
const gw = nw +
|
|
1053
|
+
const gw = nw + 4; // border + num + spaces around sign
|
|
1054
|
+
const half = Math.floor(tw / 2);
|
|
1043
1055
|
const cw = Math.max(12, half - gw);
|
|
1044
1056
|
const canHL = diff.chars <= MAX_HL_CHARS && vis.length * 2 <= MAX_RENDER_LINES * 2;
|
|
1045
1057
|
// Build separate code blocks per side
|
|
@@ -1051,22 +1063,24 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1051
1063
|
rightSrc.push(r.right.content);
|
|
1052
1064
|
}
|
|
1053
1065
|
const [leftHL, rightHL] = canHL
|
|
1054
|
-
? await Promise.all([
|
|
1066
|
+
? await Promise.all([
|
|
1067
|
+
hlBlock(leftSrc.join("\n"), language),
|
|
1068
|
+
hlBlock(rightSrc.join("\n"), language),
|
|
1069
|
+
])
|
|
1055
1070
|
: [leftSrc, rightSrc];
|
|
1056
1071
|
let lI = 0, rI = 0;
|
|
1057
1072
|
let stripeRow = 0; // tracks row index for diagonal stripe offset
|
|
1058
1073
|
function half_build(line, hl, ranges, side) {
|
|
1059
|
-
// Empty filler —
|
|
1074
|
+
// Empty filler — render nothing to avoid dead-column gaps.
|
|
1060
1075
|
if (!line) {
|
|
1061
|
-
|
|
1062
|
-
const gPat = FG_STRIPE + "╱".repeat(gw2) + RST;
|
|
1063
|
-
const g = ` ${gPat}${FG_RULE}│${RST} `;
|
|
1064
|
-
return { gutter: g, contGutter: g, bodyRows: [stripes(cw, stripeRow)] };
|
|
1076
|
+
return { gutter: "", contGutter: "", bodyRows: [""] };
|
|
1065
1077
|
}
|
|
1066
1078
|
// Hunk separator with optional function context
|
|
1067
1079
|
if (line.type === "sep") {
|
|
1068
1080
|
const label = sepLabelSplit(getSepStyle(), line.hunkMeta, line.newNum, line.content);
|
|
1069
|
-
|
|
1081
|
+
if (!label)
|
|
1082
|
+
return { gutter: "", contGutter: "", bodyRows: [""] };
|
|
1083
|
+
const g = `${BG_BASE} ${FG_DIM}${fit("", nw + 3)}${RST}`;
|
|
1070
1084
|
return { gutter: g, contGutter: g, bodyRows: [`${BG_BASE}${FG_DIM}${fit(label, cw)}${RST}`] };
|
|
1071
1085
|
}
|
|
1072
1086
|
const isDel = line.type === "del", isAdd = line.type === "add";
|
|
@@ -1074,7 +1088,13 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1074
1088
|
const cBg = isDel ? BG_DEL : isAdd ? BG_ADD : BG_BASE;
|
|
1075
1089
|
const sFg = isDel ? dc.fgDel : isAdd ? dc.fgAdd : dc.fgCtx;
|
|
1076
1090
|
const sign = isDel ? "-" : isAdd ? "+" : " ";
|
|
1077
|
-
const num = isDel
|
|
1091
|
+
const num = isDel
|
|
1092
|
+
? line.oldNum
|
|
1093
|
+
: isAdd
|
|
1094
|
+
? line.newNum
|
|
1095
|
+
: side === "left"
|
|
1096
|
+
? line.oldNum
|
|
1097
|
+
: line.newNum;
|
|
1078
1098
|
// Border bar + colored line numbers for changed lines
|
|
1079
1099
|
const borderFg = isDel ? dc.fgDel : isAdd ? dc.fgAdd : "";
|
|
1080
1100
|
const border = borderFg ? `${borderFg}${getBorderBar()}${RST}` : ` ${BG_BASE}`;
|
|
@@ -1084,13 +1104,13 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1084
1104
|
body = injectBg(hl, ranges, cBg, isDel ? BG_DEL_W : BG_ADD_W);
|
|
1085
1105
|
}
|
|
1086
1106
|
else if (isDel || isAdd) {
|
|
1087
|
-
body =
|
|
1107
|
+
body = injectBg(hl, [], cBg, cBg);
|
|
1088
1108
|
}
|
|
1089
1109
|
else {
|
|
1090
1110
|
body = `${BG_BASE}${DIM}${hl}`;
|
|
1091
1111
|
}
|
|
1092
|
-
const gutter = `${border}${gBg}${lnum(num, nw, numFg)}${
|
|
1093
|
-
const contGutter = `${border}${gBg}${" ".repeat(nw +
|
|
1112
|
+
const gutter = `${border}${gBg}${lnum(num, nw, numFg)}${gBg} ${sFg}${sign}${gBg} ${RST}`;
|
|
1113
|
+
const contGutter = `${border}${gBg}${" ".repeat(nw + 3)}${RST}`;
|
|
1094
1114
|
const bodyRows = wrapAnsi(tabs(body), cw, adaptiveWrapRows(), cBg);
|
|
1095
1115
|
return { gutter, contGutter, bodyRows };
|
|
1096
1116
|
}
|
|
@@ -1098,8 +1118,7 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1098
1118
|
// Column headers — "old" / "new" positioned above line numbers
|
|
1099
1119
|
const hdrOld = `${BG_BASE}${" ".repeat(Math.max(0, nw - 2))}${dc.fgDel}${DIM}old${RST}`;
|
|
1100
1120
|
const hdrNew = `${BG_BASE}${" ".repeat(Math.max(0, nw - 2))}${dc.fgAdd}${DIM}new${RST}`;
|
|
1101
|
-
out.push(`${BG_BASE}${hdrOld}${" ".repeat(Math.max(0, half - nw
|
|
1102
|
-
out.push(`${rule(half)}${FG_RULE}┊${RST}${rule(half)}`);
|
|
1121
|
+
out.push(`${BG_BASE}${hdrOld}${" ".repeat(Math.max(0, half - nw))}${hdrNew}`);
|
|
1103
1122
|
for (const r of vis) {
|
|
1104
1123
|
const leftLine = r.left, rightLine = r.right;
|
|
1105
1124
|
const paired = leftLine && rightLine && leftLine.type === "del" && rightLine.type === "add";
|
|
@@ -1124,20 +1143,19 @@ async function renderSplit(diff, language, max = MAX_PREVIEW_LINES, dc = DEFAULT
|
|
|
1124
1143
|
lResult = half_build(leftLine, lhl, null, "left");
|
|
1125
1144
|
rResult = half_build(rightLine, rhl, null, "right");
|
|
1126
1145
|
}
|
|
1127
|
-
// Compose wrapped rows
|
|
1146
|
+
// Compose wrapped rows; missing sides render empty to avoid dead-column gaps.
|
|
1128
1147
|
const maxRows = Math.max(lResult.bodyRows.length, rResult.bodyRows.length);
|
|
1129
|
-
const leftIsEmpty = !r.left;
|
|
1130
|
-
const rightIsEmpty = !r.right;
|
|
1131
1148
|
for (let row = 0; row < maxRows; row++) {
|
|
1132
1149
|
const lg = row === 0 ? lResult.gutter : lResult.contGutter;
|
|
1133
1150
|
const rg = row === 0 ? rResult.gutter : rResult.contGutter;
|
|
1134
|
-
const lb = lResult.bodyRows[row] ??
|
|
1135
|
-
const rb = rResult.bodyRows[row] ??
|
|
1136
|
-
|
|
1151
|
+
const lb = lResult.bodyRows[row] ?? "";
|
|
1152
|
+
const rb = rResult.bodyRows[row] ?? "";
|
|
1153
|
+
if (!lg && !rg && !lb && !rb)
|
|
1154
|
+
continue;
|
|
1155
|
+
out.push(`${lg}${lb.trimEnd()}${rg}${rb.trimEnd()}`);
|
|
1137
1156
|
stripeRow++;
|
|
1138
1157
|
}
|
|
1139
1158
|
}
|
|
1140
|
-
out.push(`${rule(half)}${FG_RULE}┊${RST}${rule(half)}`);
|
|
1141
1159
|
if (rows.length > vis.length) {
|
|
1142
1160
|
out.push(`${BG_BASE}${FG_DIM} … ${rows.length - vis.length} more lines${RST}`);
|
|
1143
1161
|
}
|
|
@@ -1180,6 +1198,70 @@ export default async function diffRendererExtension(pi) {
|
|
|
1180
1198
|
const cwd = process.cwd();
|
|
1181
1199
|
const home = process.env.HOME ?? "";
|
|
1182
1200
|
const sp = (p) => shortPath(cwd, home, p);
|
|
1201
|
+
const TOOL_HEADER_LEFT_PAD = 2;
|
|
1202
|
+
const TOOL_HEADER_TOP_PAD = 1;
|
|
1203
|
+
const TOOL_PREVIEW_BOTTOM_PAD = 1;
|
|
1204
|
+
const DIFF_BODY_LEFT_PAD = 1;
|
|
1205
|
+
function resolvePreviewDiffColors(theme) {
|
|
1206
|
+
resolveDiffColors(theme);
|
|
1207
|
+
return resolveSharedDiffColors(theme);
|
|
1208
|
+
}
|
|
1209
|
+
function bgLine(content, width) {
|
|
1210
|
+
const renderWidth = Math.max(1, width);
|
|
1211
|
+
const padding = " ".repeat(Math.max(0, renderWidth - strip(content).length));
|
|
1212
|
+
return injectBg(`${content}${padding}`, [], BG_BASE, BG_BASE);
|
|
1213
|
+
}
|
|
1214
|
+
function formatToolHeader(summary, width) {
|
|
1215
|
+
const leftPad = " ".repeat(TOOL_HEADER_LEFT_PAD);
|
|
1216
|
+
const meta = `${leftPad}${summary}`;
|
|
1217
|
+
return bgLine(meta, width);
|
|
1218
|
+
}
|
|
1219
|
+
function formatToolTitle(label, filePath, theme, width, suffix = "") {
|
|
1220
|
+
const leftPad = " ".repeat(TOOL_HEADER_LEFT_PAD);
|
|
1221
|
+
const displayPath = filePath ? ` ${theme.fg("accent", sp(filePath))}` : "";
|
|
1222
|
+
const top = Array.from({ length: TOOL_HEADER_TOP_PAD }, () => bgLine("", width));
|
|
1223
|
+
return [
|
|
1224
|
+
...top,
|
|
1225
|
+
bgLine(`${leftPad}${theme.fg("toolTitle", theme.bold(label))}${displayPath}${suffix}`, width),
|
|
1226
|
+
].join("\n");
|
|
1227
|
+
}
|
|
1228
|
+
function formatBottomPadding(width) {
|
|
1229
|
+
return Array.from({ length: TOOL_PREVIEW_BOTTOM_PAD }, () => bgLine("", width)).join("\n");
|
|
1230
|
+
}
|
|
1231
|
+
function padDiffBody(rendered) {
|
|
1232
|
+
const leftPad = `${BG_BASE}${" ".repeat(DIFF_BODY_LEFT_PAD)}${RST}`;
|
|
1233
|
+
return rendered
|
|
1234
|
+
.split("\n")
|
|
1235
|
+
.map((line) => `${leftPad}${line}`)
|
|
1236
|
+
.join("\n");
|
|
1237
|
+
}
|
|
1238
|
+
async function renderPaddedDiff(diff, language, maxLines, colors, width) {
|
|
1239
|
+
const bodyWidth = Math.max(1, width - DIFF_BODY_LEFT_PAD);
|
|
1240
|
+
return padDiffBody(await renderSharedSplit(diff, language, maxLines, colors, bodyWidth));
|
|
1241
|
+
}
|
|
1242
|
+
function diffLineCountLabel(diffLineCount, theme) {
|
|
1243
|
+
if (typeof diffLineCount !== "number")
|
|
1244
|
+
return "";
|
|
1245
|
+
return ` ${theme.fg("muted", `(${diffLineCount} diff lines)`)}`;
|
|
1246
|
+
}
|
|
1247
|
+
function setToolHeaderText(text, meta, theme) {
|
|
1248
|
+
resolvePreviewDiffColors(theme);
|
|
1249
|
+
text.__piDiffTask = undefined;
|
|
1250
|
+
const width = termW();
|
|
1251
|
+
text.setText(`${formatToolHeader(meta, width)}\n${formatBottomPadding(width)}`);
|
|
1252
|
+
}
|
|
1253
|
+
function setDiffPreviewTask(text, keyPrefix, meta, diff, language, maxLines, theme, ctx) {
|
|
1254
|
+
const themeKey = sharedThemeCacheKey(theme);
|
|
1255
|
+
const colors = resolvePreviewDiffColors(theme);
|
|
1256
|
+
const header = (width) => formatToolHeader(meta, width);
|
|
1257
|
+
text.__piDiffTask = {
|
|
1258
|
+
placeholder: `${header(termW())}\n${padDiffBody(theme.fg("muted", "rendering diff…"))}\n${formatBottomPadding(termW())}`,
|
|
1259
|
+
fallback: `${header(termW())}\n${formatBottomPadding(termW())}`,
|
|
1260
|
+
invalidate: ctx.invalidate,
|
|
1261
|
+
key: (width) => `${keyPrefix}:${themeKey}:${width}:${meta}:${diff.lines.length}:${language ?? ""}`,
|
|
1262
|
+
render: async (width) => `${header(width)}\n${await renderPaddedDiff(diff, language, maxLines, colors, width)}\n${formatBottomPadding(width)}`,
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1183
1265
|
/** Wrap a Text component so its render(width) kicks off async diff rendering
|
|
1184
1266
|
* using the real TUI width (which accounts for the sidebar). */
|
|
1185
1267
|
function getWidthAwareText(lastComponent) {
|
|
@@ -1214,7 +1296,7 @@ export default async function diffRendererExtension(pi) {
|
|
|
1214
1296
|
});
|
|
1215
1297
|
}
|
|
1216
1298
|
}
|
|
1217
|
-
return text.__piDiffRender(width);
|
|
1299
|
+
return text.__piDiffRender?.(width) ?? [];
|
|
1218
1300
|
};
|
|
1219
1301
|
return text;
|
|
1220
1302
|
}
|
|
@@ -1263,7 +1345,11 @@ Examples:
|
|
|
1263
1345
|
try {
|
|
1264
1346
|
const { existingCode, filePath, patchText, fileContent } = params ?? {};
|
|
1265
1347
|
if (!existingCode || !filePath) {
|
|
1266
|
-
return {
|
|
1348
|
+
return {
|
|
1349
|
+
content: [
|
|
1350
|
+
{ type: "text", text: "Error: existingCode and filePath are required" },
|
|
1351
|
+
],
|
|
1352
|
+
};
|
|
1267
1353
|
}
|
|
1268
1354
|
// Fetch git diff if patchText not provided
|
|
1269
1355
|
let patch = patchText;
|
|
@@ -1289,25 +1375,42 @@ Examples:
|
|
|
1289
1375
|
}
|
|
1290
1376
|
catch {
|
|
1291
1377
|
return {
|
|
1292
|
-
content: [
|
|
1378
|
+
content: [
|
|
1379
|
+
{ type: "text", text: `Error: could not read git diff for ${filePath}` },
|
|
1380
|
+
],
|
|
1293
1381
|
};
|
|
1294
1382
|
}
|
|
1295
1383
|
}
|
|
1296
1384
|
if (!patch) {
|
|
1297
1385
|
return {
|
|
1298
|
-
content: [
|
|
1386
|
+
content: [
|
|
1387
|
+
{
|
|
1388
|
+
type: "text",
|
|
1389
|
+
text: `No diff found for ${filePath} — file may not have changes`,
|
|
1390
|
+
},
|
|
1391
|
+
],
|
|
1299
1392
|
details: { unresolved: true },
|
|
1300
1393
|
};
|
|
1301
1394
|
}
|
|
1302
1395
|
const result = resolveLinesFromPatch(existingCode, patch, fileContent);
|
|
1303
1396
|
if ("unresolved" in result) {
|
|
1304
1397
|
return {
|
|
1305
|
-
content: [
|
|
1398
|
+
content: [
|
|
1399
|
+
{
|
|
1400
|
+
type: "text",
|
|
1401
|
+
text: `Could not resolve line numbers for existing_code in ${filePath}`,
|
|
1402
|
+
},
|
|
1403
|
+
],
|
|
1306
1404
|
details: { unresolved: true },
|
|
1307
1405
|
};
|
|
1308
1406
|
}
|
|
1309
1407
|
return {
|
|
1310
|
-
content: [
|
|
1408
|
+
content: [
|
|
1409
|
+
{
|
|
1410
|
+
type: "text",
|
|
1411
|
+
text: `Resolved ${filePath}:${result.startLine}-${result.endLine}`,
|
|
1412
|
+
},
|
|
1413
|
+
],
|
|
1311
1414
|
details: { startLine: result.startLine, endLine: result.endLine },
|
|
1312
1415
|
};
|
|
1313
1416
|
}
|
|
@@ -1367,11 +1470,24 @@ Examples:
|
|
|
1367
1470
|
const useFull = !!params._expandGaps;
|
|
1368
1471
|
const diff = parseDiff(old, content, useFull ? undefined : 3);
|
|
1369
1472
|
const lg = detectDiffLanguage(fp);
|
|
1370
|
-
result.details = {
|
|
1473
|
+
result.details = {
|
|
1474
|
+
_type: "diff",
|
|
1475
|
+
summary: summarize(diff.added, diff.removed),
|
|
1476
|
+
filePath: fp,
|
|
1477
|
+
diff,
|
|
1478
|
+
language: lg,
|
|
1479
|
+
oldContent: old,
|
|
1480
|
+
newContent: content,
|
|
1481
|
+
};
|
|
1371
1482
|
}
|
|
1372
1483
|
else if (old === null) {
|
|
1373
1484
|
const lineCount = content ? content.split("\n").length : 0;
|
|
1374
|
-
result.details = {
|
|
1485
|
+
result.details = {
|
|
1486
|
+
_type: "new",
|
|
1487
|
+
lines: lineCount,
|
|
1488
|
+
content: content ?? "",
|
|
1489
|
+
filePath: fp,
|
|
1490
|
+
};
|
|
1375
1491
|
}
|
|
1376
1492
|
else if (old === content) {
|
|
1377
1493
|
result.details = { _type: "noChange" };
|
|
@@ -1431,15 +1547,7 @@ Examples:
|
|
|
1431
1547
|
}
|
|
1432
1548
|
const d = result.details;
|
|
1433
1549
|
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
|
-
};
|
|
1550
|
+
setDiffPreviewTask(text, "wd", d.summary, d.diff, d.language, MAX_RENDER_LINES, theme, ctx);
|
|
1443
1551
|
return text;
|
|
1444
1552
|
}
|
|
1445
1553
|
if (d?._type === "noChange") {
|
|
@@ -1486,13 +1594,29 @@ Examples:
|
|
|
1486
1594
|
if (Array.isArray(input?.edits)) {
|
|
1487
1595
|
return input.edits
|
|
1488
1596
|
.map((edit) => ({
|
|
1489
|
-
oldText: typeof edit?.oldText === "string"
|
|
1490
|
-
|
|
1597
|
+
oldText: typeof edit?.oldText === "string"
|
|
1598
|
+
? edit.oldText
|
|
1599
|
+
: typeof edit?.old_text === "string"
|
|
1600
|
+
? edit.old_text
|
|
1601
|
+
: "",
|
|
1602
|
+
newText: typeof edit?.newText === "string"
|
|
1603
|
+
? edit.newText
|
|
1604
|
+
: typeof edit?.new_text === "string"
|
|
1605
|
+
? edit.new_text
|
|
1606
|
+
: "",
|
|
1491
1607
|
}))
|
|
1492
1608
|
.filter((edit) => edit.oldText && edit.oldText !== edit.newText);
|
|
1493
1609
|
}
|
|
1494
|
-
const oldText = typeof input?.oldText === "string"
|
|
1495
|
-
|
|
1610
|
+
const oldText = typeof input?.oldText === "string"
|
|
1611
|
+
? input.oldText
|
|
1612
|
+
: typeof input?.old_text === "string"
|
|
1613
|
+
? input.old_text
|
|
1614
|
+
: "";
|
|
1615
|
+
const newText = typeof input?.newText === "string"
|
|
1616
|
+
? input.newText
|
|
1617
|
+
: typeof input?.new_text === "string"
|
|
1618
|
+
? input.new_text
|
|
1619
|
+
: "";
|
|
1496
1620
|
return oldText && oldText !== newText ? [{ oldText, newText }] : [];
|
|
1497
1621
|
}
|
|
1498
1622
|
function summarizeEditOperations(operations) {
|
|
@@ -1553,6 +1677,7 @@ Examples:
|
|
|
1553
1677
|
details: {
|
|
1554
1678
|
_type: "editInfo",
|
|
1555
1679
|
summary,
|
|
1680
|
+
filePath: fp,
|
|
1556
1681
|
editLine,
|
|
1557
1682
|
diff: diffData,
|
|
1558
1683
|
language: lg,
|
|
@@ -1565,7 +1690,16 @@ Examples:
|
|
|
1565
1690
|
// Merge all diffs into one combined view for rendering
|
|
1566
1691
|
const merged = {
|
|
1567
1692
|
lines: diffs.flatMap((diff, i) => [
|
|
1568
|
-
...(i > 0
|
|
1693
|
+
...(i > 0
|
|
1694
|
+
? [
|
|
1695
|
+
{
|
|
1696
|
+
type: "sep",
|
|
1697
|
+
oldNum: null,
|
|
1698
|
+
newNum: null,
|
|
1699
|
+
content: `───── Edit ${i + 1} ─────`,
|
|
1700
|
+
},
|
|
1701
|
+
]
|
|
1702
|
+
: []),
|
|
1569
1703
|
...diff.lines,
|
|
1570
1704
|
]),
|
|
1571
1705
|
added: diffs.reduce((sum, diff) => sum + diff.added, 0),
|
|
@@ -1577,6 +1711,7 @@ Examples:
|
|
|
1577
1711
|
details: {
|
|
1578
1712
|
_type: "multiEditInfo",
|
|
1579
1713
|
summary,
|
|
1714
|
+
filePath: fp,
|
|
1580
1715
|
editCount: operations.length,
|
|
1581
1716
|
diffLineCount: merged.lines.length,
|
|
1582
1717
|
diff: merged,
|
|
@@ -1615,6 +1750,7 @@ Examples:
|
|
|
1615
1750
|
result.details = {
|
|
1616
1751
|
_type: "editInfo",
|
|
1617
1752
|
summary,
|
|
1753
|
+
filePath: fp,
|
|
1618
1754
|
editLine,
|
|
1619
1755
|
diff: diffData,
|
|
1620
1756
|
language: lg,
|
|
@@ -1627,7 +1763,16 @@ Examples:
|
|
|
1627
1763
|
const merged = {
|
|
1628
1764
|
lines: diffs.flatMap((diff, i) => [
|
|
1629
1765
|
// Add separator between multiple edits
|
|
1630
|
-
...(i > 0
|
|
1766
|
+
...(i > 0
|
|
1767
|
+
? [
|
|
1768
|
+
{
|
|
1769
|
+
type: "sep",
|
|
1770
|
+
oldNum: null,
|
|
1771
|
+
newNum: null,
|
|
1772
|
+
content: `───── Edit ${i + 1} ─────`,
|
|
1773
|
+
},
|
|
1774
|
+
]
|
|
1775
|
+
: []),
|
|
1631
1776
|
...diff.lines,
|
|
1632
1777
|
]),
|
|
1633
1778
|
added: diffs.reduce((sum, diff) => sum + diff.added, 0),
|
|
@@ -1637,6 +1782,7 @@ Examples:
|
|
|
1637
1782
|
result.details = {
|
|
1638
1783
|
_type: "multiEditInfo",
|
|
1639
1784
|
summary,
|
|
1785
|
+
filePath: fp,
|
|
1640
1786
|
editCount: operations.length,
|
|
1641
1787
|
diffLineCount: merged.lines.length,
|
|
1642
1788
|
diff: merged,
|
|
@@ -1648,13 +1794,13 @@ Examples:
|
|
|
1648
1794
|
const fp = args?.path ?? args?.file_path ?? "";
|
|
1649
1795
|
const operations = getEditOperations(args);
|
|
1650
1796
|
const text = ctx.lastComponent ?? new TextComponent("", 0, 0);
|
|
1651
|
-
|
|
1797
|
+
resolvePreviewDiffColors(theme);
|
|
1652
1798
|
if (ctx.argsComplete && operations.length > 0) {
|
|
1653
1799
|
const { totalAdded, totalRemoved } = summarizeEditOperations(operations);
|
|
1654
|
-
text.setText(
|
|
1800
|
+
text.setText(formatToolTitle("edit", fp, theme, termW(), ` ${theme.fg("muted", summarize(totalAdded, totalRemoved))}`));
|
|
1655
1801
|
}
|
|
1656
1802
|
else {
|
|
1657
|
-
text.setText(
|
|
1803
|
+
text.setText(formatToolTitle("edit", fp, theme, termW()));
|
|
1658
1804
|
}
|
|
1659
1805
|
return text;
|
|
1660
1806
|
},
|
|
@@ -1671,42 +1817,24 @@ Examples:
|
|
|
1671
1817
|
}
|
|
1672
1818
|
const d = result.details;
|
|
1673
1819
|
if (d?._type === "editInfo" && d.diff) {
|
|
1674
|
-
const themeKey = sharedThemeCacheKey(theme);
|
|
1675
|
-
const dc = resolveSharedDiffColors(theme);
|
|
1676
1820
|
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
|
-
};
|
|
1821
|
+
setDiffPreviewTask(text, "ed", `${d.summary}${loc}`, d.diff, d.language, MAX_PREVIEW_LINES, theme, ctx);
|
|
1684
1822
|
return text;
|
|
1685
1823
|
}
|
|
1686
1824
|
if (d?._type === "editInfo") {
|
|
1687
1825
|
const { summary: s, editLine } = d;
|
|
1688
1826
|
const loc = editLine > 0 ? ` ${theme.fg("muted", `at line ${editLine}`)}` : "";
|
|
1689
|
-
text
|
|
1690
|
-
text.setText(` ${s}${loc}`);
|
|
1827
|
+
setToolHeaderText(text, `${s}${loc}`, theme);
|
|
1691
1828
|
return text;
|
|
1692
1829
|
}
|
|
1693
1830
|
if (d?._type === "multiEditInfo") {
|
|
1694
1831
|
const { summary: s, editCount, diffLineCount, diff, language } = d;
|
|
1832
|
+
const meta = `${editCount} edits ${s}${diffLineCountLabel(diffLineCount, theme)}`;
|
|
1695
1833
|
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
|
-
};
|
|
1834
|
+
setDiffPreviewTask(text, "me", meta, diff, language, MAX_PREVIEW_LINES, theme, ctx);
|
|
1706
1835
|
return text;
|
|
1707
1836
|
}
|
|
1708
|
-
text
|
|
1709
|
-
text.setText(` ${editCount} edits ${s}${typeof diffLineCount === "number" ? ` ${theme.fg("muted", `(${diffLineCount} diff lines)`)}` : ""}`);
|
|
1837
|
+
setToolHeaderText(text, meta, theme);
|
|
1710
1838
|
return text;
|
|
1711
1839
|
}
|
|
1712
1840
|
text.__piDiffTask = undefined;
|