@heyhuynhgiabuu/pi-diff 0.1.2 → 0.1.4

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +65 -37
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyhuynhgiabuu/pi-diff",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Shiki-powered terminal diff renderer for pi — syntax-highlighted, word-level diffs in split and unified views.",
5
5
  "author": "huynhgiabuu",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -169,6 +169,16 @@ function deriveBgFromFg(fgAnsi: string, intensity: number): string {
169
169
  return `\x1b[48;2;${r};${g};${b}m`;
170
170
  }
171
171
 
172
+ /** Mix an accent color into a base color at the given intensity (0.0–1.0).
173
+ * Returns an ANSI 24-bit background escape. Used to derive diff backgrounds
174
+ * that blend with the tool box background (toolSuccessBg). */
175
+ function mixBg(base: { r: number; g: number; b: number }, accent: { r: number; g: number; b: number }, intensity: number): string {
176
+ const r = Math.round(base.r + (accent.r - base.r) * intensity);
177
+ const g = Math.round(base.g + (accent.g - base.g) * intensity);
178
+ const b = Math.round(base.b + (accent.b - base.b) * intensity);
179
+ return `\x1b[48;2;${r};${g};${b}m`;
180
+ }
181
+
172
182
  /** Whether auto-derive from theme is still pending (runs lazily on first render). */
173
183
  let _autoDerivePending = true;
174
184
 
@@ -176,37 +186,44 @@ let _autoDerivePending = true;
176
186
  let _hasExplicitBgConfig = false;
177
187
 
178
188
  /** Auto-derive all diff background colors from the pi theme's fg diff colors.
179
- * Uses different intensity levels for line bg, word highlights, and gutters. */
189
+ * Reads toolSuccessBg as the base and mixes accent colors into it.
190
+ * Falls back to black (0,0,0) as base if toolSuccessBg is unavailable. */
180
191
  function autoDeriveBgFromTheme(theme: any): void {
181
192
  if (!theme?.getFgAnsi) return;
182
193
  try {
183
194
  const fgAdd = theme.getFgAnsi("toolDiffAdded");
184
195
  const fgDel = theme.getFgAnsi("toolDiffRemoved");
196
+ const addRgb = parseAnsiRgb(fgAdd);
197
+ const delRgb = parseAnsiRgb(fgDel);
198
+ if (!addRgb || !delRgb) return;
185
199
 
186
- // Line backgrounds subtle tint (8–10% of fg color)
187
- const bgAdd = deriveBgFromFg(fgAdd, 0.08);
188
- if (bgAdd) BG_ADD = bgAdd;
189
- const bgDel = deriveBgFromFg(fgDel, 0.10);
190
- if (bgDel) BG_DEL = bgDel;
200
+ // Read toolSuccessBg as the base background color
201
+ let base = { r: 0, g: 0, b: 0 };
202
+ if (theme.getBgAnsi) {
203
+ try {
204
+ const bgAnsi = theme.getBgAnsi("toolSuccessBg");
205
+ const parsed = parseAnsiRgb(bgAnsi);
206
+ if (parsed) {
207
+ base = parsed;
208
+ BG_BASE = bgAnsi;
209
+ }
210
+ } catch { /* no toolSuccessBg — use black */ }
211
+ }
212
+
213
+ // Line backgrounds — subtle accent mixed into base (8–10%)
214
+ BG_ADD = mixBg(base, addRgb, 0.08);
215
+ BG_DEL = mixBg(base, delRgb, 0.10);
191
216
 
192
217
  // Word-level highlights — more visible (20–22%)
193
- const bgAddW = deriveBgFromFg(fgAdd, 0.20);
194
- if (bgAddW) BG_ADD_W = bgAddW;
195
- const bgDelW = deriveBgFromFg(fgDel, 0.22);
196
- if (bgDelW) BG_DEL_W = bgDelW;
218
+ BG_ADD_W = mixBg(base, addRgb, 0.20);
219
+ BG_DEL_W = mixBg(base, delRgb, 0.22);
197
220
 
198
221
  // Gutters — subtler than lines (5–6%)
199
- const bgGA = deriveBgFromFg(fgAdd, 0.05);
200
- if (bgGA) BG_GUTTER_ADD = bgGA;
201
- const bgGD = deriveBgFromFg(fgDel, 0.06);
202
- if (bgGD) BG_GUTTER_DEL = bgGD;
222
+ BG_GUTTER_ADD = mixBg(base, addRgb, 0.05);
223
+ BG_GUTTER_DEL = mixBg(base, delRgb, 0.06);
203
224
 
204
- // Empty filler neutral dark from add color luminance
205
- const addRgb = parseAnsiRgb(fgAdd);
206
- if (addRgb) {
207
- const lum = Math.round((addRgb.r * 0.3 + addRgb.g * 0.6 + addRgb.b * 0.1) * 0.04);
208
- BG_EMPTY = `\x1b[48;2;${lum};${lum};${lum}m`;
209
- }
225
+ // Empty filler and context match the base
226
+ BG_EMPTY = BG_BASE;
210
227
 
211
228
  // Rebuild derived constants
212
229
  DIVIDER = `${FG_RULE}│${RST}`;
@@ -383,7 +400,7 @@ const BORDER_BAR = "▌";
383
400
  /** Generate a dense diagonal stripe fill for empty filler cells.
384
401
  * Solid ╱ characters — uniform direction like CSS diagonal hatching. */
385
402
  function stripes(w: number, _rowOffset: number): string {
386
- return FG_STRIPE + "╱".repeat(w) + RST;
403
+ return BG_BASE + FG_STRIPE + "╱".repeat(w) + RST;
387
404
  }
388
405
 
389
406
  let DIVIDER = `${FG_RULE}│${RST}`;
@@ -392,6 +409,7 @@ const ANSI_RE = new RegExp(`${ESC_RE}\\[[0-9;]*m`, "g");
392
409
  const ANSI_CAPTURE_RE = new RegExp(`${ESC_RE}\\[([^m]*)m`, "g");
393
410
  const ANSI_PARAM_CAPTURE_RE = new RegExp(`${ESC_RE}\\[([0-9;]*)m`, "g");
394
411
  const BG_DEFAULT = "\x1b[49m"; // reset to terminal default background
412
+ let BG_BASE = BG_DEFAULT; // tool box base bg — updated from theme's toolSuccessBg
395
413
 
396
414
  // ---------------------------------------------------------------------------
397
415
  // Theme-aware diff colors
@@ -407,8 +425,18 @@ interface DiffColors {
407
425
  let DEFAULT_DIFF_COLORS: DiffColors = { fgAdd: FG_ADD, fgDel: FG_DEL, fgCtx: FG_DIM };
408
426
 
409
427
  /** Resolve diff fg colors from theme (if available), falling back to hardcoded ANSI.
410
- * On first call with a valid theme, auto-derives bg colors if no explicit config was set. */
428
+ * On first call with a valid theme, auto-derives bg colors if no explicit config was set.
429
+ * Always reads toolSuccessBg for BG_BASE (used for context line backgrounds). */
411
430
  function resolveDiffColors(theme?: any): DiffColors {
431
+ // Always read toolSuccessBg for BG_BASE (even with explicit config)
432
+ if (theme?.getBgAnsi && BG_BASE === BG_DEFAULT) {
433
+ try {
434
+ const bgAnsi = theme.getBgAnsi("toolSuccessBg");
435
+ const parsed = parseAnsiRgb(bgAnsi);
436
+ if (parsed) BG_BASE = bgAnsi;
437
+ } catch { /* ignore */ }
438
+ }
439
+
412
440
  // Auto-derive bg colors from theme on first render (if no explicit preset/overrides)
413
441
  if (_autoDerivePending && theme?.getFgAnsi) {
414
442
  autoDeriveBgFromTheme(theme);
@@ -637,7 +665,7 @@ function summarize(a: number, d: number): string {
637
665
  }
638
666
 
639
667
  function rule(w: number): string {
640
- return `${FG_RULE}${"─".repeat(w)}${RST}`;
668
+ return `${BG_BASE}${FG_RULE}${"─".repeat(w)}${RST}`;
641
669
  }
642
670
 
643
671
  /**
@@ -954,7 +982,7 @@ async function renderUnified(
954
982
  bodyBg = "",
955
983
  ): void {
956
984
  const borderFg = sign === "-" ? dc.fgDel : sign === "+" ? dc.fgAdd : "";
957
- const border = borderFg ? `${borderFg}${BORDER_BAR}${RST}` : `${BG_DEFAULT} `;
985
+ const border = borderFg ? `${borderFg}${BORDER_BAR}${RST}` : `${BG_BASE} `;
958
986
  const numFg = borderFg || FG_LNUM;
959
987
  const gutter = `${border}${gutterBg}${lnum(num, nw, numFg)}${signFg}${sign}${RST} ${DIVIDER} `;
960
988
  const contGutter = `${border}${gutterBg}${" ".repeat(nw + 1)}${RST} ${DIVIDER} `;
@@ -974,7 +1002,7 @@ async function renderUnified(
974
1002
  const pad = Math.max(0, totalW - label.length - 2);
975
1003
  const half1 = Math.floor(pad / 2),
976
1004
  half2 = pad - half1;
977
- out.push(`${FG_DIM}${"─".repeat(half1)}${label}${"─".repeat(half2)}${RST}`);
1005
+ out.push(`${BG_BASE}${FG_DIM}${"─".repeat(half1)}${label}${"─".repeat(half2)}${RST}`);
978
1006
  idx++;
979
1007
  continue;
980
1008
  }
@@ -982,7 +1010,7 @@ async function renderUnified(
982
1010
  // Context line — dimmed, single line number
983
1011
  if (l.type === "ctx") {
984
1012
  const hl = oldHL[oI] ?? l.content;
985
- emitRow(l.newNum, " ", BG_DEFAULT, dc.fgCtx, `${BG_DEFAULT}${DIM}${hl}`, BG_DEFAULT);
1013
+ emitRow(l.newNum, " ", BG_BASE, dc.fgCtx, `${BG_BASE}${DIM}${hl}`, BG_BASE);
986
1014
  oI++;
987
1015
  nI++;
988
1016
  idx++;
@@ -1034,7 +1062,7 @@ async function renderUnified(
1034
1062
 
1035
1063
  out.push(rule(tw));
1036
1064
  if (diff.lines.length > vis.length) {
1037
- out.push(`${FG_DIM} … ${diff.lines.length - vis.length} more lines${RST}`);
1065
+ out.push(`${BG_BASE}${FG_DIM} … ${diff.lines.length - vis.length} more lines${RST}`);
1038
1066
  }
1039
1067
  return out.join("\n");
1040
1068
  }
@@ -1120,21 +1148,21 @@ async function renderSplit(
1120
1148
  if (line.type === "sep") {
1121
1149
  const gap = line.newNum;
1122
1150
  const label = gap && gap > 0 ? `··· ${gap} lines ···` : "···";
1123
- const g = ` ${FG_DIM}${fit("", nw + 2)}${RST}${FG_RULE}│${RST} `;
1124
- return { gutter: g, contGutter: g, bodyRows: [`${FG_DIM}${fit(label, cw)}${RST}`] };
1151
+ const g = `${BG_BASE} ${FG_DIM}${fit("", nw + 2)}${RST}${FG_RULE}│${RST} `;
1152
+ return { gutter: g, contGutter: g, bodyRows: [`${BG_BASE}${FG_DIM}${fit(label, cw)}${RST}`] };
1125
1153
  }
1126
1154
 
1127
1155
  const isDel = line.type === "del",
1128
1156
  isAdd = line.type === "add";
1129
- const gBg = isDel ? BG_GUTTER_DEL : isAdd ? BG_GUTTER_ADD : BG_DEFAULT;
1130
- const cBg = isDel ? BG_DEL : isAdd ? BG_ADD : BG_DEFAULT;
1157
+ const gBg = isDel ? BG_GUTTER_DEL : isAdd ? BG_GUTTER_ADD : BG_BASE;
1158
+ const cBg = isDel ? BG_DEL : isAdd ? BG_ADD : BG_BASE;
1131
1159
  const sFg = isDel ? dc.fgDel : isAdd ? dc.fgAdd : dc.fgCtx;
1132
1160
  const sign = isDel ? "-" : isAdd ? "+" : " ";
1133
1161
  const num = isDel ? line.oldNum : isAdd ? line.newNum : side === "left" ? line.oldNum : line.newNum;
1134
1162
 
1135
1163
  // Border bar + colored line numbers for changed lines
1136
1164
  const borderFg = isDel ? dc.fgDel : isAdd ? dc.fgAdd : "";
1137
- const border = borderFg ? `${borderFg}${BORDER_BAR}${RST}` : ` ${BG_DEFAULT}`;
1165
+ const border = borderFg ? `${borderFg}${BORDER_BAR}${RST}` : ` ${BG_BASE}`;
1138
1166
  const numFg = borderFg || FG_LNUM;
1139
1167
 
1140
1168
  let body: string;
@@ -1143,7 +1171,7 @@ async function renderSplit(
1143
1171
  } else if (isDel || isAdd) {
1144
1172
  body = `${cBg}${hl}`;
1145
1173
  } else {
1146
- body = `${BG_DEFAULT}${DIM}${hl}`;
1174
+ body = `${BG_BASE}${DIM}${hl}`;
1147
1175
  }
1148
1176
 
1149
1177
  const gutter = `${border}${gBg}${lnum(num, nw, numFg)}${sFg}${BOLD}${sign}${RST} ${FG_RULE}│${RST} `;
@@ -1154,9 +1182,9 @@ async function renderSplit(
1154
1182
 
1155
1183
  const out: string[] = [];
1156
1184
  // Column headers — "old" / "new" positioned above line numbers
1157
- const hdrOld = `${" ".repeat(Math.max(0, nw - 2))}${dc.fgDel}${DIM}old${RST}`;
1158
- const hdrNew = `${" ".repeat(Math.max(0, nw - 2))}${dc.fgAdd}${DIM}new${RST}`;
1159
- out.push(`${hdrOld}${" ".repeat(Math.max(0, half - nw - 1))}${FG_RULE}┊${RST}${hdrNew}`);
1185
+ const hdrOld = `${BG_BASE}${" ".repeat(Math.max(0, nw - 2))}${dc.fgDel}${DIM}old${RST}`;
1186
+ const hdrNew = `${BG_BASE}${" ".repeat(Math.max(0, nw - 2))}${dc.fgAdd}${DIM}new${RST}`;
1187
+ out.push(`${BG_BASE}${hdrOld}${" ".repeat(Math.max(0, half - nw - 1))}${FG_RULE}┊${RST}${hdrNew}`);
1160
1188
  out.push(`${rule(half)}${FG_RULE}┊${RST}${rule(half)}`);
1161
1189
 
1162
1190
  for (const r of vis) {
@@ -1202,7 +1230,7 @@ async function renderSplit(
1202
1230
 
1203
1231
  out.push(`${rule(half)}${FG_RULE}┊${RST}${rule(half)}`);
1204
1232
  if (rows.length > vis.length) {
1205
- out.push(`${FG_DIM} … ${rows.length - vis.length} more lines${RST}`);
1233
+ out.push(`${BG_BASE}${FG_DIM} … ${rows.length - vis.length} more lines${RST}`);
1206
1234
  }
1207
1235
  return out.join("\n");
1208
1236
  }