@pi-archimedes/diff 2.2.0 → 2.4.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": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "type": "module",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -24,6 +24,8 @@
24
24
  "@earendil-works/pi-tui": ">=0.1.0"
25
25
  },
26
26
  "devDependencies": {
27
+ "@earendil-works/pi-coding-agent": "^0.84.2",
28
+ "@earendil-works/pi-tui": "^0.84.2",
27
29
  "@types/diff": "^7.0.2",
28
30
  "typescript": "^6.0.0"
29
31
  },
package/src/ansi/codes.ts CHANGED
@@ -7,6 +7,7 @@ import { deriveBgFromTheme } from "./colors.js";
7
7
  // ---------------------------------------------------------------------------
8
8
 
9
9
  export const RST = "\x1b[0m";
10
+ export const FG_RST = "\x1b[39m"; // reset foreground only — safe inside background-painted lines
10
11
  export const BOLD = "\x1b[1m";
11
12
  export const DIM = "\x1b[2m";
12
13
 
package/src/ansi/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /** ANSI utilities — barrel re-export. */
2
2
 
3
3
  export {
4
- RST, BOLD, DIM,
4
+ RST, FG_RST, BOLD, DIM,
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,
package/src/ansi/manip.ts CHANGED
@@ -109,7 +109,7 @@ export function shortPath(cwd: string, home: string, p: string): string {
109
109
  /** Summarize added/removed counts as colored `+N -M` string. */
110
110
  export function summarize(a: number, d: number): string {
111
111
  const p: string[] = [];
112
- if (a > 0) p.push(`${C.FG_ADD}+${a}${C.RST}`);
113
- if (d > 0) p.push(`${C.FG_DEL}-${d}${C.RST}`);
114
- return p.length ? p.join(" ") : `${C.FG_DIM}no changes${C.RST}`;
112
+ if (a > 0) p.push(`${C.FG_ADD}+${a}${C.FG_RST}`);
113
+ if (d > 0) p.push(`${C.FG_DEL}-${d}${C.FG_RST}`);
114
+ return p.length ? p.join(" ") : `${C.FG_DIM}no changes${C.FG_RST}`;
115
115
  }
@@ -0,0 +1,24 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import defaultExport from "./index.js";
3
+
4
+ describe("diff default export (standalone factory)", () => {
5
+ it("is a function that registers diff tools on session_start with a per-session theme", () => {
6
+ expect(typeof defaultExport).toBe("function");
7
+
8
+ const on = vi.fn();
9
+ const registerTool = vi.fn();
10
+ const pi = { on, registerTool } as any;
11
+
12
+ defaultExport(pi);
13
+
14
+ const startHandler = on.mock.calls.find((c: Array<unknown>) => c[0] === "session_start")?.[1];
15
+ expect(typeof startHandler).toBe("function");
16
+
17
+ // Mirror of meta's wiring: registerDiffTools(pi, getTheme, readConfig)
18
+ // runs per session with the session's ctx.theme.
19
+ startHandler!({}, { ui: { theme: {} } });
20
+
21
+ // write/edit tool overrides registered when the SDK loaded
22
+ expect(registerTool).toHaveBeenCalled();
23
+ });
24
+ });
package/src/index.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Adapted from pi-ui-hephaestus diff renderer.
5
5
  */
6
6
 
7
- import type { ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
7
+ import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
8
8
  import type { SettingItem } from "@earendil-works/pi-tui";
9
9
 
10
10
  import { setConfigGetter as setShikiConfig } from "./shiki.js";
@@ -122,3 +122,15 @@ export function registerDiffTools(
122
122
  // Register edit tool override
123
123
  registerEditTool(pi, cwd, home, _sdk.createEditTool);
124
124
  }
125
+
126
+ // ── Default export (for standalone pi.extensions loading) ─────────────────
127
+
128
+ // Diff tools need a per-session theme, so registration runs on session_start
129
+ // (mirroring meta's wiring: registerDiffTools with a theme getter + config
130
+ // reader). Meta supplies a settings-backed config loader; standalone uses the
131
+ // package defaults.
132
+ export default function (pi: ExtensionAPI): void {
133
+ pi.on("session_start", (_event, ctx: ExtensionContext) => {
134
+ registerDiffTools(pi, () => ctx.ui.theme, () => DEFAULT_DIFF_CONFIG);
135
+ });
136
+ }
package/src/tools/edit.ts CHANGED
@@ -143,28 +143,48 @@ export function registerEditTool(
143
143
  renderResult(result: any, _opt: any, theme: any, ctx: any) {
144
144
  // Update the call Box background to reflect final state.
145
145
  const box = ctx.state.callBox as Box | undefined;
146
- if (box) {
147
- const bgKey = ctx.isError ? "toolErrorBg" : "toolSuccessBg";
148
- box.setBgFn((s: string) => theme.bg(bgKey, s));
149
- }
150
- // Show summary below the diff
146
+ const bgKey = ctx.isError ? "toolErrorBg" : "toolSuccessBg";
147
+ if (box) box.setBgFn((s: string) => theme.bg(bgKey, s));
148
+
149
+ // Build the line to show below the diff.
150
+ let line: string;
151
151
  if (ctx.isError) {
152
152
  const e = result.content
153
153
  ?.filter((c: any) => c.type === "text")
154
154
  .map((c: any) => c.text || "")
155
155
  .join("\n") ?? "Error";
156
- return new Text(`\n${theme.fg("error", e)}`, 1, 0);
157
- }
158
- if (result.details?._type === "editInfo") {
156
+ line = theme.fg("error", e);
157
+ } else if (result.details?._type === "editInfo") {
159
158
  const { summary: s, editLine } = result.details;
160
159
  const loc = editLine > 0 ? ` ${theme.fg("muted", `at line ${editLine}`)}` : "";
161
- return new Text(` ${s}${loc}`, 1, 0);
162
- }
163
- if (result.details?._type === "multiEditInfo") {
160
+ line = `${s}${loc}`;
161
+ } else if (result.details?._type === "multiEditInfo") {
164
162
  const { summary: s, editCount, diffLineCount } = result.details;
165
- return new Text(` ${editCount} edits ${s}${typeof diffLineCount === "number" ? ` ${theme.fg("muted", `(${diffLineCount} diff lines)`)}` : ""}`, 1, 0);
163
+ line = `${editCount} edits ${s}${typeof diffLineCount === "number" ? ` ${theme.fg("muted", `(${diffLineCount} diff lines)`)}` : ""}`;
164
+ } else {
165
+ return new Text("", 0, 0);
166
+ }
167
+
168
+ // Render inside the call Box so the line sits on the tool background —
169
+ // a sibling Text would fall through to the raw terminal background.
170
+ // Escape sequences terminating with a full \x1b[0m reset would kill the
171
+ // Box-painted background mid-line, so downgrade them to fg-only resets.
172
+ // Guard against re-adding on every updateDisplay() pass. Must return
173
+ // an EMPTY Text (pi re-adds the returned component as a sibling —
174
+ // returning the attached Text would render the line twice).
175
+ const safeLine = line.replace(/\x1b\[0m/g, Ansi.FG_RST);
176
+ const key = `${bgKey}|${safeLine}`;
177
+ const prev = ctx.state._resText as Text | undefined;
178
+ const empty = new Text("", 0, 0);
179
+ if (prev && ctx.state._resKey === key && box && box.children.includes(prev)) return empty;
180
+ const t = new Text(safeLine, 0, 0);
181
+ if (box) {
182
+ if (prev) box.removeChild(prev);
183
+ box.addChild(t);
166
184
  }
167
- return new Text("", 0, 0);
185
+ ctx.state._resText = t;
186
+ ctx.state._resKey = key;
187
+ return box ? empty : t;
168
188
  },
169
189
  });
170
190
  }