@heyhuynhgiabuu/pi-diff 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +117 -0
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +55 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/core/diff.d.ts +14 -0
  7. package/dist/core/diff.d.ts.map +1 -0
  8. package/dist/core/diff.js +72 -0
  9. package/dist/core/diff.js.map +1 -0
  10. package/dist/index.d.ts +42 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +1738 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/review/command.d.ts +2 -0
  15. package/dist/review/command.d.ts.map +1 -0
  16. package/dist/review/command.js +365 -0
  17. package/dist/review/command.js.map +1 -0
  18. package/dist/review/export.d.ts +7 -0
  19. package/dist/review/export.d.ts.map +1 -0
  20. package/dist/review/export.js +77 -0
  21. package/dist/review/export.js.map +1 -0
  22. package/dist/review/file-preview.d.ts +9 -0
  23. package/dist/review/file-preview.d.ts.map +1 -0
  24. package/dist/review/file-preview.js +228 -0
  25. package/dist/review/file-preview.js.map +1 -0
  26. package/dist/review/git.d.ts +49 -0
  27. package/dist/review/git.d.ts.map +1 -0
  28. package/dist/review/git.js +200 -0
  29. package/dist/review/git.js.map +1 -0
  30. package/dist/review/hunk-preview.d.ts +25 -0
  31. package/dist/review/hunk-preview.d.ts.map +1 -0
  32. package/dist/review/hunk-preview.js +973 -0
  33. package/dist/review/hunk-preview.js.map +1 -0
  34. package/dist/review/interactive.d.ts +27 -0
  35. package/dist/review/interactive.d.ts.map +1 -0
  36. package/dist/review/interactive.js +146 -0
  37. package/dist/review/interactive.js.map +1 -0
  38. package/dist/review/model.d.ts +33 -0
  39. package/dist/review/model.d.ts.map +1 -0
  40. package/dist/review/model.js +208 -0
  41. package/dist/review/model.js.map +1 -0
  42. package/dist/review/prompt.d.ts +4 -0
  43. package/dist/review/prompt.d.ts.map +1 -0
  44. package/dist/review/prompt.js +43 -0
  45. package/dist/review/prompt.js.map +1 -0
  46. package/dist/review/session.d.ts +69 -0
  47. package/dist/review/session.d.ts.map +1 -0
  48. package/dist/review/session.js +190 -0
  49. package/dist/review/session.js.map +1 -0
  50. package/dist/review/tui.d.ts +57 -0
  51. package/dist/review/tui.d.ts.map +1 -0
  52. package/dist/review/tui.js +486 -0
  53. package/dist/review/tui.js.map +1 -0
  54. package/media/review-diff.png +0 -0
  55. package/package.json +69 -5
  56. package/prompts/review-diff-agent.md +19 -0
  57. package/biome.json +0 -29
  58. package/src/index.ts +0 -1635
  59. package/tsconfig.json +0 -19
@@ -0,0 +1,973 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.renderReviewHunkPreview = renderReviewHunkPreview;
37
+ exports.applyDiffPalette = applyDiffPalette;
38
+ exports.themeCacheKey = themeCacheKey;
39
+ exports.resolveDiffColors = resolveDiffColors;
40
+ exports.lang = lang;
41
+ exports.renderUnified = renderUnified;
42
+ exports.renderSplit = renderSplit;
43
+ const node_fs_1 = require("node:fs");
44
+ const node_path_1 = require("node:path");
45
+ const cli_1 = require("@shikijs/cli");
46
+ const Diff = __importStar(require("diff"));
47
+ const DIFF_PRESETS = {
48
+ default: {
49
+ name: "default",
50
+ description: "Original pi-diff colors — tuned for dark theme bases (~#1e1e2e)",
51
+ bgAdd: "#162620",
52
+ bgDel: "#2d1919",
53
+ bgAddHighlight: "#234b32",
54
+ bgDelHighlight: "#502323",
55
+ bgGutterAdd: "#12201a",
56
+ bgGutterDel: "#261616",
57
+ bgEmpty: "#121212",
58
+ fgDim: "#505050",
59
+ fgLnum: "#646464",
60
+ fgRule: "#323232",
61
+ fgStripe: "#282828",
62
+ fgSafeMuted: "#8b949e",
63
+ },
64
+ midnight: {
65
+ name: "midnight",
66
+ description: "Subtle tints for pure black (#000000) terminal backgrounds",
67
+ bgAdd: "#0d1a12",
68
+ bgDel: "#1a0d0d",
69
+ bgAddHighlight: "#1a3825",
70
+ bgDelHighlight: "#381a1a",
71
+ bgGutterAdd: "#091208",
72
+ bgGutterDel: "#120908",
73
+ bgEmpty: "#080808",
74
+ fgDim: "#404040",
75
+ fgLnum: "#505050",
76
+ fgRule: "#282828",
77
+ fgStripe: "#1e1e1e",
78
+ fgSafeMuted: "#8b949e",
79
+ },
80
+ subtle: {
81
+ name: "subtle",
82
+ description: "Minimal backgrounds — barely-there tints for a clean look",
83
+ bgAdd: "#081008",
84
+ bgDel: "#100808",
85
+ bgAddHighlight: "#122818",
86
+ bgDelHighlight: "#281212",
87
+ bgGutterAdd: "#060c06",
88
+ bgGutterDel: "#0c0606",
89
+ bgEmpty: "#060606",
90
+ fgDim: "#383838",
91
+ fgLnum: "#484848",
92
+ fgRule: "#242424",
93
+ fgStripe: "#181818",
94
+ fgSafeMuted: "#8b949e",
95
+ },
96
+ neon: {
97
+ name: "neon",
98
+ description: "Higher contrast backgrounds for better visibility",
99
+ bgAdd: "#1a3320",
100
+ bgDel: "#331a16",
101
+ bgAddHighlight: "#2d5c3a",
102
+ bgDelHighlight: "#5c2d2d",
103
+ bgGutterAdd: "#142818",
104
+ bgGutterDel: "#28120e",
105
+ bgEmpty: "#141414",
106
+ fgDim: "#606060",
107
+ fgLnum: "#787878",
108
+ fgRule: "#404040",
109
+ fgStripe: "#303030",
110
+ fgSafeMuted: "#9da5ae",
111
+ },
112
+ };
113
+ const SPLIT_MIN_WIDTH = envInt("DIFF_SPLIT_MIN_WIDTH", 150);
114
+ const SPLIT_MIN_CODE_WIDTH = envInt("DIFF_SPLIT_MIN_CODE_WIDTH", 60);
115
+ const SPLIT_MAX_WRAP_RATIO = 0.2;
116
+ const SPLIT_MAX_WRAP_LINES = 8;
117
+ const MAX_HL_CHARS = 80_000;
118
+ const CACHE_LIMIT = 192;
119
+ const WORD_DIFF_MIN_SIM = 0.15;
120
+ const MAX_WRAP_ROWS_WIDE = 3;
121
+ const MAX_WRAP_ROWS_MED = 2;
122
+ const MAX_WRAP_ROWS_NARROW = 1;
123
+ const DEFAULT_RENDER_WIDTH = 120;
124
+ const MIN_RENDER_WIDTH = 40;
125
+ let RST = "\x1b[0m";
126
+ const BOLD = "\x1b[1m";
127
+ const DIM = "\x1b[2m";
128
+ let BG_ADD = envBg("DIFF_BG_ADD", "\x1b[48;2;22;38;32m");
129
+ let BG_DEL = envBg("DIFF_BG_DEL", "\x1b[48;2;45;25;25m");
130
+ let BG_ADD_W = envBg("DIFF_BG_ADD_HL", "\x1b[48;2;35;75;50m");
131
+ let BG_DEL_W = envBg("DIFF_BG_DEL_HL", "\x1b[48;2;80;35;35m");
132
+ let BG_GUTTER_ADD = envBg("DIFF_BG_GUTTER_ADD", "\x1b[48;2;18;32;26m");
133
+ let BG_GUTTER_DEL = envBg("DIFF_BG_GUTTER_DEL", "\x1b[48;2;38;22;22m");
134
+ let BG_EMPTY = "\x1b[48;2;18;18;18m";
135
+ let FG_ADD = envFg("DIFF_FG_ADD", "\x1b[38;2;100;180;120m");
136
+ let FG_DEL = envFg("DIFF_FG_DEL", "\x1b[38;2;200;100;100m");
137
+ let FG_DIM = "\x1b[38;2;80;80;80m";
138
+ let FG_LNUM = "\x1b[38;2;100;100;100m";
139
+ let FG_RULE = "\x1b[38;2;50;50;50m";
140
+ let FG_SAFE_MUTED = "\x1b[38;2;139;148;158m";
141
+ let FG_STRIPE = "\x1b[38;2;40;40;40m";
142
+ const BORDER_BAR = "▌";
143
+ let DIVIDER = `${FG_RULE}│${RST}`;
144
+ const ESC_RE = "\u001b";
145
+ const ANSI_RE = new RegExp(`${ESC_RE}\\[[0-9;]*m`, "g");
146
+ const ANSI_CAPTURE_RE = new RegExp(`${ESC_RE}\\[([^m]*)m`, "g");
147
+ const ANSI_PARAM_CAPTURE_RE = new RegExp(`${ESC_RE}\\[([0-9;]*)m`, "g");
148
+ const BG_DEFAULT = "\x1b[49m";
149
+ let BG_BASE = BG_DEFAULT;
150
+ let DEFAULT_DIFF_COLORS = { fgAdd: FG_ADD, fgDel: FG_DEL, fgCtx: FG_DIM };
151
+ let _lastResolvedThemeKey = "";
152
+ let _autoDerivePending = true;
153
+ let _hasExplicitBgConfig = false;
154
+ let THEME = process.env.DIFF_THEME ?? "github-dark";
155
+ let paletteApplied = false;
156
+ const EXT_LANG = {
157
+ ts: "typescript",
158
+ tsx: "tsx",
159
+ js: "javascript",
160
+ jsx: "jsx",
161
+ mjs: "javascript",
162
+ cjs: "javascript",
163
+ py: "python",
164
+ rb: "ruby",
165
+ rs: "rust",
166
+ go: "go",
167
+ java: "java",
168
+ c: "c",
169
+ cpp: "cpp",
170
+ h: "c",
171
+ hpp: "cpp",
172
+ cs: "csharp",
173
+ swift: "swift",
174
+ kt: "kotlin",
175
+ html: "html",
176
+ css: "css",
177
+ scss: "scss",
178
+ json: "json",
179
+ yaml: "yaml",
180
+ yml: "yaml",
181
+ toml: "toml",
182
+ md: "markdown",
183
+ sql: "sql",
184
+ sh: "bash",
185
+ bash: "bash",
186
+ zsh: "bash",
187
+ lua: "lua",
188
+ php: "php",
189
+ dart: "dart",
190
+ xml: "xml",
191
+ graphql: "graphql",
192
+ svelte: "svelte",
193
+ vue: "vue",
194
+ };
195
+ (0, cli_1.codeToANSI)("", "typescript", THEME).catch(() => { });
196
+ const highlightCache = new Map();
197
+ async function renderReviewHunkPreview(input) {
198
+ ensurePalette();
199
+ const diff = createParsedDiffFromReviewHunk(input.hunk);
200
+ const width = Math.max(MIN_RENDER_WIDTH, input.width || DEFAULT_RENDER_WIDTH);
201
+ const language = lang(input.filePath);
202
+ const colors = resolveDiffColors(input.theme);
203
+ return renderUnified(diff, language, input.maxLines ?? diff.lines.length, colors, width);
204
+ }
205
+ function ensurePalette() {
206
+ if (paletteApplied)
207
+ return;
208
+ applyDiffPalette();
209
+ paletteApplied = true;
210
+ }
211
+ function createParsedDiffFromReviewHunk(hunk) {
212
+ let added = 0;
213
+ let removed = 0;
214
+ let chars = 0;
215
+ for (const line of hunk.lines) {
216
+ chars += line.content.length;
217
+ if (line.type === "add")
218
+ added += 1;
219
+ if (line.type === "del")
220
+ removed += 1;
221
+ }
222
+ return {
223
+ lines: hunk.lines.map((line) => ({
224
+ type: line.type,
225
+ oldNum: line.oldNum,
226
+ newNum: line.newNum,
227
+ content: line.content,
228
+ })),
229
+ added,
230
+ removed,
231
+ chars,
232
+ };
233
+ }
234
+ function envInt(name, fallback) {
235
+ const value = Number.parseInt(process.env[name] ?? "", 10);
236
+ return Number.isFinite(value) && value > 0 ? value : fallback;
237
+ }
238
+ function envFg(name, fallback) {
239
+ const hex = process.env[name];
240
+ if (!hex || !/^#[0-9a-fA-F]{6}$/.test(hex))
241
+ return fallback;
242
+ const r = Number.parseInt(hex.slice(1, 3), 16);
243
+ const g = Number.parseInt(hex.slice(3, 5), 16);
244
+ const b = Number.parseInt(hex.slice(5, 7), 16);
245
+ return `\x1b[38;2;${r};${g};${b}m`;
246
+ }
247
+ function envBg(name, fallback) {
248
+ const hex = process.env[name];
249
+ if (!hex || !/^#[0-9a-fA-F]{6}$/.test(hex))
250
+ return fallback;
251
+ const r = Number.parseInt(hex.slice(1, 3), 16);
252
+ const g = Number.parseInt(hex.slice(3, 5), 16);
253
+ const b = Number.parseInt(hex.slice(5, 7), 16);
254
+ return `\x1b[48;2;${r};${g};${b}m`;
255
+ }
256
+ function parseAnsiRgb(ansi) {
257
+ const match = ansi.match(new RegExp(`${ESC_RE}\\[(?:38|48);2;(\\d+);(\\d+);(\\d+)m`));
258
+ return match ? { r: Number(match[1]), g: Number(match[2]), b: Number(match[3]) } : null;
259
+ }
260
+ function hexToBgAnsi(hex) {
261
+ if (!hex || !/^#[0-9a-fA-F]{6}$/.test(hex))
262
+ return "";
263
+ const r = Number.parseInt(hex.slice(1, 3), 16);
264
+ const g = Number.parseInt(hex.slice(3, 5), 16);
265
+ const b = Number.parseInt(hex.slice(5, 7), 16);
266
+ return `\x1b[48;2;${r};${g};${b}m`;
267
+ }
268
+ function hexToFgAnsi(hex) {
269
+ if (!hex || !/^#[0-9a-fA-F]{6}$/.test(hex))
270
+ return "";
271
+ const r = Number.parseInt(hex.slice(1, 3), 16);
272
+ const g = Number.parseInt(hex.slice(3, 5), 16);
273
+ const b = Number.parseInt(hex.slice(5, 7), 16);
274
+ return `\x1b[38;2;${r};${g};${b}m`;
275
+ }
276
+ function mixBg(base, accent, intensity) {
277
+ const r = Math.round(base.r + (accent.r - base.r) * intensity);
278
+ const g = Math.round(base.g + (accent.g - base.g) * intensity);
279
+ const b = Math.round(base.b + (accent.b - base.b) * intensity);
280
+ return `\x1b[48;2;${r};${g};${b}m`;
281
+ }
282
+ function autoDeriveBgFromTheme(theme) {
283
+ if (!theme?.getFgAnsi)
284
+ return;
285
+ try {
286
+ const fgAdd = theme.getFgAnsi("toolDiffAdded");
287
+ const fgDel = theme.getFgAnsi("toolDiffRemoved");
288
+ const addRgb = parseAnsiRgb(fgAdd);
289
+ const delRgb = parseAnsiRgb(fgDel);
290
+ if (!addRgb || !delRgb)
291
+ return;
292
+ let addBase = { r: 0, g: 0, b: 0 };
293
+ let delBase = addBase;
294
+ if (theme.getBgAnsi) {
295
+ try {
296
+ const successBgAnsi = theme.getBgAnsi("toolSuccessBg");
297
+ const successParsed = parseAnsiRgb(successBgAnsi);
298
+ if (successParsed) {
299
+ addBase = successParsed;
300
+ delBase = successParsed;
301
+ BG_BASE = successBgAnsi;
302
+ }
303
+ }
304
+ catch { }
305
+ try {
306
+ const errorParsed = parseAnsiRgb(theme.getBgAnsi("toolErrorBg"));
307
+ if (errorParsed)
308
+ delBase = errorParsed;
309
+ }
310
+ catch { }
311
+ }
312
+ BG_ADD = mixBg(addBase, addRgb, 0.08);
313
+ BG_DEL = mixBg(delBase, delRgb, 0.1);
314
+ BG_ADD_W = mixBg(addBase, addRgb, 0.2);
315
+ BG_DEL_W = mixBg(delBase, delRgb, 0.22);
316
+ BG_GUTTER_ADD = mixBg(addBase, addRgb, 0.05);
317
+ BG_GUTTER_DEL = mixBg(delBase, delRgb, 0.06);
318
+ BG_EMPTY = BG_BASE;
319
+ RST = `\x1b[0m${BG_BASE}`;
320
+ DIVIDER = `${FG_RULE}│${RST}`;
321
+ }
322
+ catch { }
323
+ }
324
+ function loadDiffConfig() {
325
+ const paths = [`${process.cwd()}/.pi/settings.json`, `${process.env.HOME ?? ""}/.pi/settings.json`];
326
+ for (const path of paths) {
327
+ try {
328
+ if ((0, node_fs_1.existsSync)(path)) {
329
+ const raw = JSON.parse((0, node_fs_1.readFileSync)(path, "utf-8"));
330
+ if (raw.diffTheme || raw.diffColors) {
331
+ return { diffTheme: raw.diffTheme, diffColors: raw.diffColors };
332
+ }
333
+ }
334
+ }
335
+ catch { }
336
+ }
337
+ return {};
338
+ }
339
+ function applyDiffPalette() {
340
+ const config = loadDiffConfig();
341
+ const preset = config.diffTheme ? DIFF_PRESETS[config.diffTheme] : null;
342
+ if (preset)
343
+ _hasExplicitBgConfig = true;
344
+ const overrides = config.diffColors ?? {};
345
+ if (Object.keys(overrides).length > 0)
346
+ _hasExplicitBgConfig = true;
347
+ const applyBg = (envName, key, presetValue, set) => {
348
+ if (envName && process.env[envName])
349
+ return;
350
+ const hex = overrides[key] ?? presetValue;
351
+ if (hex) {
352
+ const ansi = hexToBgAnsi(hex);
353
+ if (ansi)
354
+ set(ansi);
355
+ }
356
+ };
357
+ const applyFg = (envName, key, presetValue, set) => {
358
+ if (envName && process.env[envName])
359
+ return;
360
+ const hex = overrides[key] ?? presetValue;
361
+ if (hex) {
362
+ const ansi = hexToFgAnsi(hex);
363
+ if (ansi)
364
+ set(ansi);
365
+ }
366
+ };
367
+ applyBg("DIFF_BG_ADD", "bgAdd", preset?.bgAdd, (value) => {
368
+ BG_ADD = value;
369
+ });
370
+ applyBg("DIFF_BG_DEL", "bgDel", preset?.bgDel, (value) => {
371
+ BG_DEL = value;
372
+ });
373
+ applyBg("DIFF_BG_ADD_HL", "bgAddHighlight", preset?.bgAddHighlight, (value) => {
374
+ BG_ADD_W = value;
375
+ });
376
+ applyBg("DIFF_BG_DEL_HL", "bgDelHighlight", preset?.bgDelHighlight, (value) => {
377
+ BG_DEL_W = value;
378
+ });
379
+ applyBg("DIFF_BG_GUTTER_ADD", "bgGutterAdd", preset?.bgGutterAdd, (value) => {
380
+ BG_GUTTER_ADD = value;
381
+ });
382
+ applyBg("DIFF_BG_GUTTER_DEL", "bgGutterDel", preset?.bgGutterDel, (value) => {
383
+ BG_GUTTER_DEL = value;
384
+ });
385
+ applyBg(null, "bgEmpty", preset?.bgEmpty, (value) => {
386
+ BG_EMPTY = value;
387
+ });
388
+ applyFg("DIFF_FG_ADD", "fgAdd", preset?.fgAdd, (value) => {
389
+ FG_ADD = value;
390
+ });
391
+ applyFg("DIFF_FG_DEL", "fgDel", preset?.fgDel, (value) => {
392
+ FG_DEL = value;
393
+ });
394
+ applyFg(null, "fgDim", preset?.fgDim, (value) => {
395
+ FG_DIM = value;
396
+ });
397
+ applyFg(null, "fgLnum", preset?.fgLnum, (value) => {
398
+ FG_LNUM = value;
399
+ });
400
+ applyFg(null, "fgRule", preset?.fgRule, (value) => {
401
+ FG_RULE = value;
402
+ });
403
+ applyFg(null, "fgStripe", preset?.fgStripe, (value) => {
404
+ FG_STRIPE = value;
405
+ });
406
+ applyFg(null, "fgSafeMuted", preset?.fgSafeMuted, (value) => {
407
+ FG_SAFE_MUTED = value;
408
+ });
409
+ const shikiTheme = overrides.shikiTheme ?? preset?.shikiTheme;
410
+ if (shikiTheme)
411
+ THEME = shikiTheme;
412
+ DIVIDER = `${FG_RULE}│${RST}`;
413
+ DEFAULT_DIFF_COLORS = { fgAdd: FG_ADD, fgDel: FG_DEL, fgCtx: FG_DIM };
414
+ _autoDerivePending = !_hasExplicitBgConfig;
415
+ }
416
+ function themeCacheKey(theme) {
417
+ if (!theme?.fg)
418
+ return "no-theme";
419
+ const fgKeys = [
420
+ "toolTitle",
421
+ "accent",
422
+ "muted",
423
+ "success",
424
+ "error",
425
+ "toolDiffAdded",
426
+ "toolDiffRemoved",
427
+ "toolDiffContext",
428
+ ];
429
+ const bgKeys = ["toolSuccessBg", "toolErrorBg"];
430
+ const parts = [];
431
+ for (const key of fgKeys) {
432
+ try {
433
+ parts.push(theme.fg(key, key));
434
+ }
435
+ catch {
436
+ parts.push(key);
437
+ }
438
+ }
439
+ for (const key of bgKeys) {
440
+ try {
441
+ parts.push(theme.bg ? theme.bg(key, key) : key);
442
+ }
443
+ catch {
444
+ parts.push(key);
445
+ }
446
+ }
447
+ return parts.join("|");
448
+ }
449
+ function resolveDiffColors(theme) {
450
+ const currentThemeKey = themeCacheKey(theme);
451
+ if (!_hasExplicitBgConfig && _lastResolvedThemeKey && _lastResolvedThemeKey !== currentThemeKey) {
452
+ BG_BASE = BG_DEFAULT;
453
+ RST = "\x1b[0m";
454
+ _autoDerivePending = true;
455
+ }
456
+ _lastResolvedThemeKey = currentThemeKey;
457
+ if (theme?.getBgAnsi && BG_BASE === BG_DEFAULT) {
458
+ try {
459
+ const bgAnsi = theme.getBgAnsi("toolSuccessBg");
460
+ const parsed = parseAnsiRgb(bgAnsi);
461
+ if (parsed) {
462
+ BG_BASE = bgAnsi;
463
+ RST = `\x1b[0m${BG_BASE}`;
464
+ }
465
+ }
466
+ catch { }
467
+ }
468
+ if (_autoDerivePending && theme?.getFgAnsi) {
469
+ autoDeriveBgFromTheme(theme);
470
+ _autoDerivePending = false;
471
+ }
472
+ if (!theme?.getFgAnsi)
473
+ return DEFAULT_DIFF_COLORS;
474
+ try {
475
+ return {
476
+ fgAdd: theme.getFgAnsi("toolDiffAdded") || FG_ADD,
477
+ fgDel: theme.getFgAnsi("toolDiffRemoved") || FG_DEL,
478
+ fgCtx: theme.getFgAnsi("toolDiffContext") || FG_DIM,
479
+ };
480
+ }
481
+ catch {
482
+ return DEFAULT_DIFF_COLORS;
483
+ }
484
+ }
485
+ function strip(content) {
486
+ return content.replace(ANSI_RE, "");
487
+ }
488
+ function tabs(content) {
489
+ return content.replace(/\t/g, " ");
490
+ }
491
+ function adaptiveWrapRows(width) {
492
+ if (width >= 180)
493
+ return MAX_WRAP_ROWS_WIDE;
494
+ if (width >= 120)
495
+ return MAX_WRAP_ROWS_MED;
496
+ return MAX_WRAP_ROWS_NARROW;
497
+ }
498
+ function fit(content, width) {
499
+ if (width <= 0)
500
+ return "";
501
+ const plain = strip(content);
502
+ if (plain.length <= width)
503
+ return content + " ".repeat(width - plain.length);
504
+ const showWidth = width > 2 ? width - 1 : width;
505
+ let visible = 0;
506
+ let index = 0;
507
+ while (index < content.length && visible < showWidth) {
508
+ if (content[index] === "\x1b") {
509
+ const end = content.indexOf("m", index);
510
+ if (end !== -1) {
511
+ index = end + 1;
512
+ continue;
513
+ }
514
+ }
515
+ visible += 1;
516
+ index += 1;
517
+ }
518
+ return width > 2 ? `${content.slice(0, index)}${RST}${FG_DIM}›${RST}` : `${content.slice(0, index)}${RST}`;
519
+ }
520
+ function ansiState(content) {
521
+ let fg = "";
522
+ let bg = "";
523
+ for (const match of content.matchAll(ANSI_CAPTURE_RE)) {
524
+ const params = match[1] ?? "";
525
+ const sequence = match[0] ?? "";
526
+ if (params === "0") {
527
+ fg = "";
528
+ bg = "";
529
+ }
530
+ else if (params === "39") {
531
+ fg = "";
532
+ }
533
+ else if (params.startsWith("38;")) {
534
+ fg = sequence;
535
+ }
536
+ else if (params.startsWith("48;")) {
537
+ bg = sequence;
538
+ }
539
+ }
540
+ return bg + fg;
541
+ }
542
+ function isLowContrastShikiFg(params) {
543
+ if (params === "30" || params === "90")
544
+ return true;
545
+ if (params === "38;5;0" || params === "38;5;8")
546
+ return true;
547
+ if (!params.startsWith("38;2;"))
548
+ return false;
549
+ const parts = params.split(";").map(Number);
550
+ if (parts.length !== 5 || parts.some((value) => !Number.isFinite(value)))
551
+ return false;
552
+ const [, , r, g, b] = parts;
553
+ const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
554
+ return luminance < 72;
555
+ }
556
+ function normalizeShikiContrast(ansi) {
557
+ return ansi.replace(ANSI_PARAM_CAPTURE_RE, (sequence, params) => isLowContrastShikiFg(params) ? FG_SAFE_MUTED : sequence);
558
+ }
559
+ function wrapAnsi(content, width, maxRows, fillBg = "") {
560
+ if (width <= 0)
561
+ return [""];
562
+ const plain = strip(content);
563
+ if (plain.length <= width) {
564
+ const padding = width - plain.length;
565
+ return padding > 0 ? [content + fillBg + " ".repeat(padding) + (fillBg ? RST : "")] : [content];
566
+ }
567
+ const rows = [];
568
+ let row = "";
569
+ let visible = 0;
570
+ let index = 0;
571
+ let onLastRow = false;
572
+ let effectiveWidth = width;
573
+ while (index < content.length) {
574
+ if (!onLastRow && rows.length >= maxRows - 1) {
575
+ onLastRow = true;
576
+ effectiveWidth = width > 2 ? width - 1 : width;
577
+ }
578
+ if (content[index] === "\x1b") {
579
+ const end = content.indexOf("m", index);
580
+ if (end !== -1) {
581
+ row += content.slice(index, end + 1);
582
+ index = end + 1;
583
+ continue;
584
+ }
585
+ }
586
+ if (visible >= effectiveWidth) {
587
+ if (onLastRow) {
588
+ let hasMore = false;
589
+ for (let cursor = index; cursor < content.length; cursor++) {
590
+ if (content[cursor] === "\x1b") {
591
+ const escapeEnd = content.indexOf("m", cursor);
592
+ if (escapeEnd !== -1) {
593
+ cursor = escapeEnd;
594
+ continue;
595
+ }
596
+ }
597
+ hasMore = true;
598
+ break;
599
+ }
600
+ if (hasMore && width > 2)
601
+ row += `${RST}${FG_DIM}›${RST}`;
602
+ else
603
+ row += fillBg + " ".repeat(Math.max(0, width - visible)) + RST;
604
+ rows.push(row);
605
+ return rows;
606
+ }
607
+ const state = ansiState(row);
608
+ rows.push(row + RST);
609
+ row = state + fillBg;
610
+ visible = 0;
611
+ if (rows.length >= maxRows - 1) {
612
+ onLastRow = true;
613
+ effectiveWidth = width > 2 ? width - 1 : width;
614
+ }
615
+ }
616
+ row += content[index];
617
+ visible += 1;
618
+ index += 1;
619
+ }
620
+ if (row.length > 0 || rows.length === 0) {
621
+ rows.push(row + fillBg + " ".repeat(Math.max(0, width - visible)) + RST);
622
+ }
623
+ return rows;
624
+ }
625
+ function lnum(value, width, fg = FG_LNUM) {
626
+ if (value === null)
627
+ return " ".repeat(width);
628
+ const text = String(value);
629
+ return `${fg}${" ".repeat(Math.max(0, width - text.length))}${text}${RST}`;
630
+ }
631
+ function rule(width) {
632
+ return `${BG_BASE}${FG_RULE}${"─".repeat(width)}${RST}`;
633
+ }
634
+ function shouldUseSplit(diff, width, maxRows) {
635
+ if (!diff.lines.length)
636
+ return false;
637
+ if (width < SPLIT_MIN_WIDTH)
638
+ return false;
639
+ const numberWidth = Math.max(2, String(Math.max(...diff.lines.map((line) => line.oldNum ?? line.newNum ?? 0), 0)).length);
640
+ const half = Math.floor((width - 1) / 2);
641
+ const gutterWidth = numberWidth + 5;
642
+ const codeWidth = Math.max(12, half - gutterWidth);
643
+ if (codeWidth < SPLIT_MIN_CODE_WIDTH)
644
+ return false;
645
+ const visibleLines = diff.lines.slice(0, maxRows);
646
+ let contentLines = 0;
647
+ let wrapCandidates = 0;
648
+ for (const line of visibleLines) {
649
+ if (line.type === "sep")
650
+ continue;
651
+ contentLines += 1;
652
+ if (tabs(line.content).length > codeWidth)
653
+ wrapCandidates += 1;
654
+ }
655
+ if (contentLines === 0)
656
+ return true;
657
+ const wrapRatio = wrapCandidates / contentLines;
658
+ if (wrapCandidates >= SPLIT_MAX_WRAP_LINES)
659
+ return false;
660
+ if (wrapRatio >= SPLIT_MAX_WRAP_RATIO)
661
+ return false;
662
+ return true;
663
+ }
664
+ function lang(filePath) {
665
+ return EXT_LANG[(0, node_path_1.extname)(filePath).slice(1).toLowerCase()];
666
+ }
667
+ function touchCache(key, value) {
668
+ highlightCache.delete(key);
669
+ highlightCache.set(key, value);
670
+ while (highlightCache.size > CACHE_LIMIT) {
671
+ const first = highlightCache.keys().next().value;
672
+ if (first === undefined)
673
+ break;
674
+ highlightCache.delete(first);
675
+ }
676
+ return value;
677
+ }
678
+ async function hlBlock(code, language) {
679
+ if (!code)
680
+ return [""];
681
+ if (!language || code.length > MAX_HL_CHARS)
682
+ return code.split("\n");
683
+ const key = `${THEME}\0${language}\0${code}`;
684
+ const cached = highlightCache.get(key);
685
+ if (cached)
686
+ return touchCache(key, cached);
687
+ try {
688
+ const ansi = normalizeShikiContrast(await (0, cli_1.codeToANSI)(code, language, THEME));
689
+ const output = (ansi.endsWith("\n") ? ansi.slice(0, -1) : ansi).split("\n");
690
+ return touchCache(key, output);
691
+ }
692
+ catch {
693
+ return code.split("\n");
694
+ }
695
+ }
696
+ function wordDiffAnalysis(oldText, newText) {
697
+ if (!oldText && !newText)
698
+ return { similarity: 1, oldRanges: [], newRanges: [] };
699
+ const parts = Diff.diffWords(oldText, newText);
700
+ const oldRanges = [];
701
+ const newRanges = [];
702
+ let oldPosition = 0;
703
+ let newPosition = 0;
704
+ let same = 0;
705
+ for (const part of parts) {
706
+ if (part.removed) {
707
+ oldRanges.push([oldPosition, oldPosition + part.value.length]);
708
+ oldPosition += part.value.length;
709
+ }
710
+ else if (part.added) {
711
+ newRanges.push([newPosition, newPosition + part.value.length]);
712
+ newPosition += part.value.length;
713
+ }
714
+ else {
715
+ const length = part.value.length;
716
+ same += length;
717
+ oldPosition += length;
718
+ newPosition += length;
719
+ }
720
+ }
721
+ const maxLength = Math.max(oldText.length, newText.length);
722
+ return { similarity: maxLength > 0 ? same / maxLength : 1, oldRanges, newRanges };
723
+ }
724
+ function injectBg(ansiLine, ranges, baseBg, highlightBg) {
725
+ if (!ranges.length)
726
+ return baseBg + ansiLine + RST;
727
+ let output = baseBg;
728
+ let visible = 0;
729
+ let inHighlight = false;
730
+ let rangeIndex = 0;
731
+ let index = 0;
732
+ while (index < ansiLine.length) {
733
+ if (ansiLine[index] === "\x1b") {
734
+ const end = ansiLine.indexOf("m", index);
735
+ if (end !== -1) {
736
+ const sequence = ansiLine.slice(index, end + 1);
737
+ output += sequence;
738
+ if (sequence === "\x1b[0m")
739
+ output += inHighlight ? highlightBg : baseBg;
740
+ index = end + 1;
741
+ continue;
742
+ }
743
+ }
744
+ while (rangeIndex < ranges.length && visible >= ranges[rangeIndex][1])
745
+ rangeIndex += 1;
746
+ const wantsHighlight = rangeIndex < ranges.length && visible >= ranges[rangeIndex][0] && visible < ranges[rangeIndex][1];
747
+ if (wantsHighlight !== inHighlight) {
748
+ inHighlight = wantsHighlight;
749
+ output += inHighlight ? highlightBg : baseBg;
750
+ }
751
+ output += ansiLine[index];
752
+ visible += 1;
753
+ index += 1;
754
+ }
755
+ return output + RST;
756
+ }
757
+ function plainWordDiff(oldText, newText) {
758
+ const parts = Diff.diffWords(oldText, newText);
759
+ let oldOutput = "";
760
+ let newOutput = "";
761
+ for (const part of parts) {
762
+ if (part.removed)
763
+ oldOutput += `${BG_DEL_W}${part.value}${RST}${BG_DEL}`;
764
+ else if (part.added)
765
+ newOutput += `${BG_ADD_W}${part.value}${RST}${BG_ADD}`;
766
+ else {
767
+ oldOutput += part.value;
768
+ newOutput += part.value;
769
+ }
770
+ }
771
+ return { old: oldOutput, new: newOutput };
772
+ }
773
+ async function renderUnified(diff, language, maxLines, colors, width) {
774
+ if (!diff.lines.length)
775
+ return "";
776
+ const visible = diff.lines.slice(0, maxLines);
777
+ const renderWidth = Math.max(MIN_RENDER_WIDTH, width);
778
+ const numberWidth = Math.max(2, String(Math.max(...visible.map((line) => line.oldNum ?? line.newNum ?? 0), 0)).length);
779
+ const gutterWidth = numberWidth + 5;
780
+ const codeWidth = Math.max(20, renderWidth - gutterWidth);
781
+ const canHighlight = diff.chars <= MAX_HL_CHARS && visible.length <= maxLines;
782
+ const oldSource = [];
783
+ const newSource = [];
784
+ for (const line of visible) {
785
+ if (line.type === "ctx" || line.type === "del")
786
+ oldSource.push(line.content);
787
+ if (line.type === "ctx" || line.type === "add")
788
+ newSource.push(line.content);
789
+ }
790
+ const [oldHighlights, newHighlights] = canHighlight
791
+ ? await Promise.all([hlBlock(oldSource.join("\n"), language), hlBlock(newSource.join("\n"), language)])
792
+ : [oldSource, newSource];
793
+ let oldIndex = 0;
794
+ let newIndex = 0;
795
+ let index = 0;
796
+ const output = [rule(renderWidth)];
797
+ function emitRow(number, sign, gutterBg, signFg, body, bodyBg = "") {
798
+ const borderFg = sign === "-" ? colors.fgDel : sign === "+" ? colors.fgAdd : "";
799
+ const border = borderFg ? `${borderFg}${BORDER_BAR}${RST}` : `${BG_BASE} `;
800
+ const numFg = borderFg || FG_LNUM;
801
+ const gutter = `${border}${gutterBg}${lnum(number, numberWidth, numFg)}${signFg}${sign}${RST} ${DIVIDER} `;
802
+ const continuationGutter = `${border}${gutterBg}${" ".repeat(numberWidth + 1)}${RST} ${DIVIDER} `;
803
+ const rows = wrapAnsi(tabs(body), codeWidth, adaptiveWrapRows(renderWidth), bodyBg);
804
+ output.push(`${gutter}${rows[0]}${RST}`);
805
+ for (let rowIndex = 1; rowIndex < rows.length; rowIndex++) {
806
+ output.push(`${continuationGutter}${rows[rowIndex]}${RST}`);
807
+ }
808
+ }
809
+ while (index < visible.length) {
810
+ const line = visible[index];
811
+ if (line.type === "sep") {
812
+ const gap = line.newNum;
813
+ const label = gap && gap > 0 ? ` ${gap} unmodified lines ` : "···";
814
+ const totalWidth = Math.min(renderWidth, 72);
815
+ const padding = Math.max(0, totalWidth - label.length - 2);
816
+ const left = Math.floor(padding / 2);
817
+ const right = padding - left;
818
+ output.push(`${BG_BASE}${FG_DIM}${"─".repeat(left)}${label}${"─".repeat(right)}${RST}`);
819
+ index += 1;
820
+ continue;
821
+ }
822
+ if (line.type === "ctx") {
823
+ const highlight = oldHighlights[oldIndex] ?? line.content;
824
+ emitRow(line.newNum, " ", BG_BASE, colors.fgCtx, `${BG_BASE}${DIM}${highlight}`, BG_BASE);
825
+ oldIndex += 1;
826
+ newIndex += 1;
827
+ index += 1;
828
+ continue;
829
+ }
830
+ const deletions = [];
831
+ while (index < visible.length && visible[index].type === "del") {
832
+ deletions.push({ line: visible[index], hl: oldHighlights[oldIndex] ?? visible[index].content });
833
+ oldIndex += 1;
834
+ index += 1;
835
+ }
836
+ const additions = [];
837
+ while (index < visible.length && visible[index].type === "add") {
838
+ additions.push({ line: visible[index], hl: newHighlights[newIndex] ?? visible[index].content });
839
+ newIndex += 1;
840
+ index += 1;
841
+ }
842
+ const isPaired = deletions.length === 1 && additions.length === 1;
843
+ const wordDiff = isPaired ? wordDiffAnalysis(deletions[0].line.content, additions[0].line.content) : null;
844
+ if (isPaired && wordDiff && wordDiff.similarity >= WORD_DIFF_MIN_SIM && canHighlight) {
845
+ const deletionBody = injectBg(deletions[0].hl, wordDiff.oldRanges, BG_DEL, BG_DEL_W);
846
+ const additionBody = injectBg(additions[0].hl, wordDiff.newRanges, BG_ADD, BG_ADD_W);
847
+ emitRow(deletions[0].line.oldNum, "-", BG_GUTTER_DEL, `${colors.fgDel}${BOLD}`, deletionBody, BG_DEL);
848
+ emitRow(additions[0].line.newNum, "+", BG_GUTTER_ADD, `${colors.fgAdd}${BOLD}`, additionBody, BG_ADD);
849
+ continue;
850
+ }
851
+ if (isPaired && wordDiff && wordDiff.similarity >= WORD_DIFF_MIN_SIM && !canHighlight) {
852
+ const plain = plainWordDiff(deletions[0].line.content, additions[0].line.content);
853
+ emitRow(deletions[0].line.oldNum, "-", BG_GUTTER_DEL, `${colors.fgDel}${BOLD}`, `${BG_DEL}${plain.old}`, BG_DEL);
854
+ emitRow(additions[0].line.newNum, "+", BG_GUTTER_ADD, `${colors.fgAdd}${BOLD}`, `${BG_ADD}${plain.new}`, BG_ADD);
855
+ continue;
856
+ }
857
+ for (const deletion of deletions) {
858
+ const body = canHighlight ? `${BG_DEL}${deletion.hl}` : `${BG_DEL}${deletion.line.content}`;
859
+ emitRow(deletion.line.oldNum, "-", BG_GUTTER_DEL, `${colors.fgDel}${BOLD}`, body, BG_DEL);
860
+ }
861
+ for (const addition of additions) {
862
+ const body = canHighlight ? `${BG_ADD}${addition.hl}` : `${BG_ADD}${addition.line.content}`;
863
+ emitRow(addition.line.newNum, "+", BG_GUTTER_ADD, `${colors.fgAdd}${BOLD}`, body, BG_ADD);
864
+ }
865
+ }
866
+ output.push(rule(renderWidth));
867
+ if (diff.lines.length > visible.length) {
868
+ output.push(`${BG_BASE}${FG_DIM} … ${diff.lines.length - visible.length} more lines${RST}`);
869
+ }
870
+ return output.join("\n");
871
+ }
872
+ async function renderSplit(diff, language, maxLines, colors, width) {
873
+ if (!shouldUseSplit(diff, width, maxLines))
874
+ return renderUnified(diff, language, maxLines, colors, width);
875
+ if (!diff.lines.length)
876
+ return "";
877
+ const rows = [];
878
+ let index = 0;
879
+ while (index < diff.lines.length) {
880
+ const line = diff.lines[index];
881
+ if (line.type === "sep" || line.type === "ctx") {
882
+ rows.push({ left: line, right: line });
883
+ index += 1;
884
+ continue;
885
+ }
886
+ const deletions = [];
887
+ const additions = [];
888
+ while (index < diff.lines.length && diff.lines[index]?.type === "del") {
889
+ const deletion = diff.lines[index];
890
+ if (deletion)
891
+ deletions.push(deletion);
892
+ index += 1;
893
+ }
894
+ while (index < diff.lines.length && diff.lines[index]?.type === "add") {
895
+ const addition = diff.lines[index];
896
+ if (addition)
897
+ additions.push(addition);
898
+ index += 1;
899
+ }
900
+ const count = Math.max(deletions.length, additions.length);
901
+ for (let rowIndex = 0; rowIndex < count; rowIndex++)
902
+ rows.push({ left: deletions[rowIndex] ?? null, right: additions[rowIndex] ?? null });
903
+ }
904
+ const visible = rows.slice(0, maxLines);
905
+ const renderWidth = Math.max(MIN_RENDER_WIDTH, width);
906
+ const half = Math.floor((renderWidth - 1) / 2);
907
+ const numberWidth = Math.max(2, String(Math.max(...diff.lines.map((line) => line.oldNum ?? line.newNum ?? 0), 0)).length);
908
+ const gutterWidth = numberWidth + 5;
909
+ const codeWidth = Math.max(12, half - gutterWidth);
910
+ const canHighlight = diff.chars <= MAX_HL_CHARS && visible.length * 2 <= maxLines * 2;
911
+ const leftSource = [];
912
+ const rightSource = [];
913
+ for (const row of visible) {
914
+ if (row.left && row.left.type !== "sep")
915
+ leftSource.push(row.left.content);
916
+ if (row.right && row.right.type !== "sep")
917
+ rightSource.push(row.right.content);
918
+ }
919
+ const [leftHighlights, rightHighlights] = canHighlight
920
+ ? await Promise.all([hlBlock(leftSource.join("\n"), language), hlBlock(rightSource.join("\n"), language)])
921
+ : [leftSource, rightSource];
922
+ let leftIndex = 0;
923
+ let rightIndex = 0;
924
+ const output = [];
925
+ output.push(`${rule(half)}${FG_RULE}┊${RST}${rule(half)}`);
926
+ const emptyBody = `${BG_EMPTY}${" ".repeat(codeWidth)}${RST}`;
927
+ function buildHalf(line, highlight, ranges, side) {
928
+ if (!line) {
929
+ const gutter = `${BG_BASE} ${" ".repeat(numberWidth + 2)}${FG_RULE}│${RST} `;
930
+ return { gutter, continuation: gutter, bodyRows: [emptyBody] };
931
+ }
932
+ if (line.type === "sep") {
933
+ const label = line.newNum && line.newNum > 0 ? `··· ${line.newNum} lines ···` : "···";
934
+ const gutter = `${BG_BASE} ${FG_DIM}${fit("", numberWidth + 2)}${RST}${FG_RULE}│${RST} `;
935
+ return { gutter, continuation: gutter, bodyRows: [`${BG_BASE}${FG_DIM}${fit(label, codeWidth)}${RST}`] };
936
+ }
937
+ const isDeletion = line.type === "del";
938
+ const isAddition = line.type === "add";
939
+ const gutterBg = isDeletion ? BG_GUTTER_DEL : isAddition ? BG_GUTTER_ADD : BG_BASE;
940
+ const codeBg = isDeletion ? BG_DEL : isAddition ? BG_ADD : BG_BASE;
941
+ const signFg = isDeletion ? colors.fgDel : isAddition ? colors.fgAdd : colors.fgCtx;
942
+ const sign = isDeletion ? "-" : isAddition ? "+" : " ";
943
+ const number = isDeletion ? line.oldNum : isAddition ? line.newNum : side === "left" ? line.oldNum : line.newNum;
944
+ const borderFg = isDeletion ? colors.fgDel : isAddition ? colors.fgAdd : "";
945
+ const border = borderFg ? `${borderFg}${BORDER_BAR}${RST}` : `${BG_BASE} `;
946
+ const numFg = borderFg || FG_LNUM;
947
+ let body = isDeletion || isAddition ? `${codeBg}${highlight}` : `${BG_BASE}${DIM}${highlight}`;
948
+ if (ranges && ranges.length > 0)
949
+ body = injectBg(highlight, ranges, codeBg, isDeletion ? BG_DEL_W : BG_ADD_W);
950
+ const gutter = `${border}${gutterBg}${lnum(number, numberWidth, numFg)}${signFg}${BOLD}${sign}${RST} ${FG_RULE}│${RST} `;
951
+ const continuation = `${border}${gutterBg}${" ".repeat(numberWidth + 1)}${RST} ${FG_RULE}│${RST} `;
952
+ return { gutter, continuation, bodyRows: wrapAnsi(tabs(body), codeWidth, adaptiveWrapRows(renderWidth), codeBg) };
953
+ }
954
+ for (const row of visible) {
955
+ const isPairedChange = row.left?.type === "del" && row.right?.type === "add";
956
+ const wordDiff = isPairedChange && row.left && row.right ? wordDiffAnalysis(row.left.content, row.right.content) : null;
957
+ const leftHighlight = row.left && row.left.type !== "sep" ? (leftHighlights[leftIndex++] ?? row.left.content) : "";
958
+ const rightHighlight = row.right && row.right.type !== "sep" ? (rightHighlights[rightIndex++] ?? row.right.content) : "";
959
+ const leftHalf = buildHalf(row.left, leftHighlight, isPairedChange && wordDiff && wordDiff.similarity >= WORD_DIFF_MIN_SIM ? wordDiff.oldRanges : null, "left");
960
+ const rightHalf = buildHalf(row.right, rightHighlight, isPairedChange && wordDiff && wordDiff.similarity >= WORD_DIFF_MIN_SIM ? wordDiff.newRanges : null, "right");
961
+ const maxRows = Math.max(leftHalf.bodyRows.length, rightHalf.bodyRows.length);
962
+ for (let rowIndex = 0; rowIndex < maxRows; rowIndex++) {
963
+ const leftBody = leftHalf.bodyRows[rowIndex] ?? emptyBody;
964
+ const rightBody = rightHalf.bodyRows[rowIndex] ?? emptyBody;
965
+ output.push(`${rowIndex === 0 ? leftHalf.gutter : leftHalf.continuation}${leftBody}${DIVIDER}${rowIndex === 0 ? rightHalf.gutter : rightHalf.continuation}${rightBody}`);
966
+ }
967
+ }
968
+ output.push(`${rule(half)}${FG_RULE}┊${RST}${rule(half)}`);
969
+ if (rows.length > visible.length)
970
+ output.push(`${BG_BASE}${FG_DIM} … ${rows.length - visible.length} more lines${RST}`);
971
+ return output.join("\n");
972
+ }
973
+ //# sourceMappingURL=hunk-preview.js.map