@pi-archimedes/diff 0.7.0 → 0.9.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-archimedes/diff",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "keywords": [
6
6
  "pi-package"
package/src/ansi/codes.ts CHANGED
@@ -54,18 +54,6 @@ export const DEFAULT_DIFF_BG: DiffBg = {
54
54
  divider: `${FG_RULE}│\x1b[0m`,
55
55
  };
56
56
 
57
- // Legacy aliases — used by word-diff.ts during transition. Will be removed.
58
- // These MUST be mutable and live in the same module as the functions that mutate them.
59
- export let BG_ADD = DEFAULT_DIFF_BG.bgAdd;
60
- export let BG_DEL = DEFAULT_DIFF_BG.bgDel;
61
- export let BG_ADD_W = DEFAULT_DIFF_BG.bgAddW;
62
- export let BG_DEL_W = DEFAULT_DIFF_BG.bgDelW;
63
- export let BG_GUTTER_ADD = DEFAULT_DIFF_BG.bgGutterAdd;
64
- export let BG_GUTTER_DEL = DEFAULT_DIFF_BG.bgGutterDel;
65
- export let BG_EMPTY = DEFAULT_DIFF_BG.bgEmpty;
66
- export let BG_BASE = DEFAULT_DIFF_BG.bgBase;
67
- export let DIVIDER = DEFAULT_DIFF_BG.divider;
68
-
69
57
  // Theme cache key state — lives here since it's used by resolveDiffColors.
70
58
  let _lastThemeKey: string | undefined;
71
59
 
@@ -91,21 +79,12 @@ export interface DiffColors {
91
79
  export const DEFAULT_DIFF_COLORS: DiffColors = { fgAdd: FG_ADD, fgDel: FG_DEL, fgCtx: FG_DIM };
92
80
 
93
81
  // ---------------------------------------------------------------------------
94
- // Theme-aware color resolution (mutates legacy aliases)
82
+ // Theme-aware color resolution
95
83
  // ---------------------------------------------------------------------------
96
84
 
97
85
  /** Reset auto-derived colors — call when theme changes. */
98
86
  export function resetDiffColors(): void {
99
87
  _lastThemeKey = undefined;
100
- BG_ADD = DEFAULT_DIFF_BG.bgAdd;
101
- BG_DEL = DEFAULT_DIFF_BG.bgDel;
102
- BG_ADD_W = DEFAULT_DIFF_BG.bgAddW;
103
- BG_DEL_W = DEFAULT_DIFF_BG.bgDelW;
104
- BG_GUTTER_ADD = DEFAULT_DIFF_BG.bgGutterAdd;
105
- BG_GUTTER_DEL = DEFAULT_DIFF_BG.bgGutterDel;
106
- BG_EMPTY = DEFAULT_DIFF_BG.bgEmpty;
107
- BG_BASE = DEFAULT_DIFF_BG.bgBase;
108
- DIVIDER = DEFAULT_DIFF_BG.divider;
109
88
  }
110
89
 
111
90
  export function themeCacheKey(theme?: any): string {
@@ -130,18 +109,8 @@ export { deriveBgFromTheme };
130
109
  export function resolveDiffColors(theme?: any): DiffColors {
131
110
  const themeKey = themeCacheKey(theme);
132
111
 
133
- // Re-derive when theme changes (different key) or first call
134
- if (themeKey !== _lastThemeKey && theme?.getFgAnsi) {
135
- const dbg = deriveBgFromTheme(theme);
136
- BG_ADD = dbg.bgAdd;
137
- BG_DEL = dbg.bgDel;
138
- BG_ADD_W = dbg.bgAddW;
139
- BG_DEL_W = dbg.bgDelW;
140
- BG_GUTTER_ADD = dbg.bgGutterAdd;
141
- BG_GUTTER_DEL = dbg.bgGutterDel;
142
- BG_EMPTY = dbg.bgEmpty;
143
- BG_BASE = dbg.bgBase;
144
- DIVIDER = dbg.divider;
112
+ // Re-derive theme key cache (bg colors are now passed explicitly via DiffBg)
113
+ if (themeKey !== _lastThemeKey) {
145
114
  _lastThemeKey = themeKey;
146
115
  }
147
116
  if (!theme?.getFgAnsi) return DEFAULT_DIFF_COLORS;
package/src/ansi/index.ts CHANGED
@@ -5,7 +5,6 @@ export {
5
5
  FG_ADD, FG_DEL, FG_DIM, FG_LNUM, FG_RULE, FG_SAFE_MUTED, FG_STRIPE,
6
6
  BORDER_BAR,
7
7
  DEFAULT_DIFF_BG,
8
- BG_ADD, BG_DEL, BG_ADD_W, BG_DEL_W, BG_GUTTER_ADD, BG_GUTTER_DEL, BG_EMPTY, BG_BASE, DIVIDER,
9
8
  ANSI_RE, ANSI_CAPTURE_RE, ANSI_PARAM_CAPTURE_RE,
10
9
  DEFAULT_DIFF_COLORS,
11
10
  resetDiffColors, themeCacheKey, resolveDiffColors,
package/src/core/diff.ts CHANGED
@@ -15,6 +15,10 @@ export interface ParsedDiff {
15
15
  }
16
16
 
17
17
  export function parseDiff(oldContent: string, newContent: string, ctx = 3): ParsedDiff {
18
+ // Early return for identical content — skip expensive diff computation
19
+ if (oldContent === newContent) {
20
+ return { lines: [], added: 0, removed: 0, chars: 0 };
21
+ }
18
22
  const patch = Diff.structuredPatch("", "", oldContent, newContent, "", "", { context: ctx });
19
23
  const lines: DiffLine[] = [];
20
24
  let added = 0;
@@ -42,8 +42,7 @@ import type { ParsedDiff } from "./core/diff.js";
42
42
  export class DiffComponent implements Component {
43
43
  private diff: ParsedDiff;
44
44
  private language: BundledLanguage | undefined;
45
- private dc: DiffColors;
46
- private dbg: DiffBg;
45
+ private theme: Theme;
47
46
  private maxLines: number;
48
47
 
49
48
  /** Box wrapper with base background — makes the diff self-contained. */
@@ -58,6 +57,9 @@ export class DiffComponent implements Component {
58
57
  /** Final cached output (box-wrapped). */
59
58
  private _cachedLines: string[] | undefined;
60
59
 
60
+ /** Theme cache key — invalidates derived colors on theme change. */
61
+ private _themeKey: string | undefined;
62
+
61
63
  constructor(
62
64
  diff: ParsedDiff,
63
65
  language: BundledLanguage | undefined,
@@ -66,12 +68,31 @@ export class DiffComponent implements Component {
66
68
  ) {
67
69
  this.diff = diff;
68
70
  this.language = language;
69
- this.dc = Ansi.resolveDiffColors(theme);
70
- this.dbg = deriveBgFromTheme(theme);
71
+ this.theme = theme;
71
72
  this.maxLines = maxLines;
72
73
 
73
74
  // Box provides base background so the diff is self-contained.
74
- this.shell = new Box(0, 0, (s: string) => this.dbg.bgBase + s + this.dbg.rst);
75
+ // Background function is re-derived on each render via _deriveBg().
76
+ this.shell = new Box(0, 0, (s: string) => this._deriveBg().bgBase + s + this._deriveBg().rst);
77
+ }
78
+
79
+ /** Re-derive colors from the current theme — handles theme changes. */
80
+ private _deriveColors(): { dc: DiffColors; dbg: DiffBg } {
81
+ const themeKey = Ansi.themeCacheKey(this.theme);
82
+ if (themeKey === this._themeKey && this._cachedDc && this._cachedDbg) {
83
+ return { dc: this._cachedDc, dbg: this._cachedDbg };
84
+ }
85
+ this._themeKey = themeKey;
86
+ this._cachedDc = Ansi.resolveDiffColors(this.theme);
87
+ this._cachedDbg = deriveBgFromTheme(this.theme);
88
+ return { dc: this._cachedDc, dbg: this._cachedDbg };
89
+ }
90
+
91
+ private _cachedDc: DiffColors | undefined;
92
+ private _cachedDbg: DiffBg | undefined;
93
+
94
+ private _deriveBg(): DiffBg {
95
+ return this._deriveColors().dbg;
75
96
  }
76
97
 
77
98
  render(width: number): string[] {
@@ -96,13 +117,14 @@ export class DiffComponent implements Component {
96
117
  }
97
118
 
98
119
  // Start async render for this width
120
+ const { dc, dbg } = this._deriveColors();
99
121
  const promise = renderSplitLines(
100
122
  this.diff,
101
123
  this.language,
102
124
  width,
103
125
  this.maxLines,
104
- this.dc,
105
- this.dbg,
126
+ dc,
127
+ dbg,
106
128
  );
107
129
  this._renderPromise = promise;
108
130
 
@@ -135,5 +157,8 @@ export class DiffComponent implements Component {
135
157
  invalidate(): void {
136
158
  this._cachedWidth = undefined;
137
159
  this._cachedLines = undefined;
160
+ this._themeKey = undefined; // Force re-derive on theme change
161
+ this._cachedDc = undefined;
162
+ this._cachedDbg = undefined;
138
163
  }
139
164
  }
package/src/index.ts CHANGED
@@ -11,6 +11,7 @@ import { setConfigGetter as setShikiConfig } from "./shiki.js";
11
11
  import { setConfigGetter as setRenderConfig } from "./render/index.js";
12
12
  import { DiffComponent } from "./diff-component.js";
13
13
  import type { DiffBg, DiffColors } from "./ansi/index.js";
14
+ import { resetDiffColors } from "./ansi/index.js";
14
15
  import { registerWriteTool, registerEditTool } from "./tools/index.js";
15
16
 
16
17
  // ---------------------------------------------------------------------------
@@ -78,6 +79,9 @@ export function registerDiffTools(
78
79
  ): void {
79
80
  _readConfig = readConfig;
80
81
 
82
+ // Reset theme-derived colors so they are re-derived from the current theme
83
+ resetDiffColors();
84
+
81
85
  // Wire config getters into submodules
82
86
  setShikiConfig(() => getConfig());
83
87
  setRenderConfig(() => getConfig());
@@ -34,20 +34,7 @@ export async function renderSplit(
34
34
  dc: DiffColors = DEFAULT_DIFF_COLORS,
35
35
  ): Promise<string> {
36
36
  const width = process.stdout.columns ?? DEFAULT_TERM_WIDTH;
37
- // Build DiffBg from current (possibly theme-derived) global aliases.
38
- const dbg: DiffBg = {
39
- bgAdd: Ansi.BG_ADD,
40
- bgDel: Ansi.BG_DEL,
41
- bgAddW: Ansi.BG_ADD_W,
42
- bgDelW: Ansi.BG_DEL_W,
43
- bgGutterAdd: Ansi.BG_GUTTER_ADD,
44
- bgGutterDel: Ansi.BG_GUTTER_DEL,
45
- bgEmpty: Ansi.BG_EMPTY,
46
- bgBase: Ansi.BG_BASE,
47
- rst: Ansi.BG_BASE === "\x1b[49m" ? "\x1b[0m" : `\x1b[0m${Ansi.BG_BASE}`,
48
- divider: Ansi.DIVIDER,
49
- };
50
- const lines = await renderSplitLines(diff, language, width, max, dc, dbg);
37
+ const lines = await renderSplitLines(diff, language, width, max, dc, DEFAULT_DIFF_BG);
51
38
  return lines.join("\n");
52
39
  }
53
40
 
@@ -160,7 +147,7 @@ export async function renderSplitLines(
160
147
  lResult = half_build(leftLine, lhl, wd.oldRanges, "left");
161
148
  rResult = half_build(rightLine, rhl, wd.newRanges, "right");
162
149
  } else if (paired && wd && wd.similarity >= WORD_DIFF_MIN_SIM && !canHL) {
163
- const pwd = plainWordDiff(leftLine.content, rightLine.content);
150
+ const pwd = plainWordDiff(leftLine.content, rightLine.content, dbg);
164
151
  lI++; rI++;
165
152
  lResult = half_build(leftLine, pwd.old, null, "left");
166
153
  rResult = half_build(rightLine, pwd.new, null, "right");
@@ -31,20 +31,7 @@ export async function renderUnified(
31
31
  dc: DiffColors = DEFAULT_DIFF_COLORS,
32
32
  ): Promise<string> {
33
33
  const width = process.stdout.columns ?? DEFAULT_TERM_WIDTH;
34
- // Build DiffBg from current (possibly theme-derived) global aliases.
35
- const dbg: DiffBg = {
36
- bgAdd: Ansi.BG_ADD,
37
- bgDel: Ansi.BG_DEL,
38
- bgAddW: Ansi.BG_ADD_W,
39
- bgDelW: Ansi.BG_DEL_W,
40
- bgGutterAdd: Ansi.BG_GUTTER_ADD,
41
- bgGutterDel: Ansi.BG_GUTTER_DEL,
42
- bgEmpty: Ansi.BG_EMPTY,
43
- bgBase: Ansi.BG_BASE,
44
- rst: Ansi.BG_BASE === "\x1b[49m" ? "\x1b[0m" : `\x1b[0m${Ansi.BG_BASE}`,
45
- divider: Ansi.DIVIDER,
46
- };
47
- const lines = await renderUnifiedLines(diff, language, width, max, dc, dbg);
34
+ const lines = await renderUnifiedLines(diff, language, width, max, dc, DEFAULT_DIFF_BG);
48
35
  return lines.join("\n");
49
36
  }
50
37
 
@@ -135,7 +122,7 @@ export async function renderUnifiedLines(
135
122
  continue;
136
123
  }
137
124
  if (isPaired && wd && wd.similarity >= WORD_DIFF_MIN_SIM && !canHL) {
138
- const pwd = plainWordDiff(dels[0]!.l.content, adds[0]!.l.content);
125
+ const pwd = plainWordDiff(dels[0]!.l.content, adds[0]!.l.content, dbg);
139
126
  emitRow(dels[0]!.l.oldNum, "-", dbg.bgGutterDel, `${dc.fgDel}${Ansi.BOLD}`, `${dbg.bgDel}${pwd.old}`, dbg.bgDel);
140
127
  emitRow(adds[0]!.l.newNum, "+", dbg.bgGutterAdd, `${dc.fgAdd}${Ansi.BOLD}`, `${dbg.bgAdd}${pwd.new}`, dbg.bgAdd);
141
128
  continue;
package/src/word-diff.ts CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as Diff from "diff";
4
4
  import * as Ansi from "./ansi/index.js";
5
+ import type { DiffBg } from "./ansi/index.js";
5
6
 
6
7
  // ---------------------------------------------------------------------------
7
8
  // Word diff analysis
@@ -107,13 +108,17 @@ export function injectBg(
107
108
  }
108
109
 
109
110
  /** Simple word diff (no syntax hl) — fallback when Shiki isn't available. */
110
- export function plainWordDiff(oldText: string, newText: string): { old: string; new: string } {
111
+ export function plainWordDiff(
112
+ oldText: string,
113
+ newText: string,
114
+ dbg: DiffBg,
115
+ ): { old: string; new: string } {
111
116
  const parts = Diff.diffWords(oldText, newText);
112
117
  let o = "",
113
118
  n = "";
114
119
  for (const p of parts) {
115
- if (p.removed) o += `${Ansi.BG_DEL_W}${p.value}${Ansi.RST}${Ansi.BG_DEL}`;
116
- else if (p.added) n += `${Ansi.BG_ADD_W}${p.value}${Ansi.RST}${Ansi.BG_ADD}`;
120
+ if (p.removed) o += `${dbg.bgDelW}${p.value}${Ansi.RST}${dbg.bgDel}`;
121
+ else if (p.added) n += `${dbg.bgAddW}${p.value}${Ansi.RST}${dbg.bgAdd}`;
117
122
  else {
118
123
  o += p.value;
119
124
  n += p.value;