@xynogen/pix-edit 0.1.21 → 0.1.25
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/README.md +1 -1
- package/package.json +4 -4
- package/src/edit.test.ts +56 -1
- package/src/edit.ts +44 -25
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ After a configurable delay (default 10 seconds), a completed diff collapses to a
|
|
|
20
20
|
}
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
Set `collapse.tools.edit: false` to disable for this tool only. See `@xynogen/pix-
|
|
23
|
+
Set `collapse.tools.edit: false` to disable for this tool only. See `@xynogen/pix-runtime/collapse` for the full API.
|
|
24
24
|
|
|
25
25
|
## Install
|
|
26
26
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xynogen/pix-edit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
4
4
|
"description": "Pi tool — precise text replacement edit with diff rendering",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@xynogen/pix-data": "^0.4.
|
|
38
|
-
"@xynogen/pix-pretty": "^1.
|
|
39
|
-
"@xynogen/pix-runtime": "^0.
|
|
37
|
+
"@xynogen/pix-data": "^0.4.3",
|
|
38
|
+
"@xynogen/pix-pretty": "^1.11.2",
|
|
39
|
+
"@xynogen/pix-runtime": "^0.5.3"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@earendil-works/pi-coding-agent": "*",
|
package/src/edit.test.ts
CHANGED
|
@@ -106,6 +106,61 @@ describe("registerEditTool", () => {
|
|
|
106
106
|
expect(result?.getText()).not.toContain("✓ edit");
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
+
it("renders single-step output inline as one compact line, framed when expanded", () => {
|
|
110
|
+
const registered: { renderResult?: (...args: unknown[]) => MockTextComponent } = {};
|
|
111
|
+
const mockPi: PiPrettyApi = {
|
|
112
|
+
registerTool(tool: unknown) {
|
|
113
|
+
Object.assign(registered, tool);
|
|
114
|
+
},
|
|
115
|
+
registerCommand() {},
|
|
116
|
+
on() {},
|
|
117
|
+
};
|
|
118
|
+
registerEditTool(
|
|
119
|
+
mockPi,
|
|
120
|
+
() => ({ execute: async () => ({ content: [], details: undefined }) }),
|
|
121
|
+
{
|
|
122
|
+
cwd: process.cwd(),
|
|
123
|
+
sp: (p: string) => p,
|
|
124
|
+
TextComponent: MockTextComponent as unknown as TextComponentCtor,
|
|
125
|
+
fffState: {
|
|
126
|
+
module: null,
|
|
127
|
+
finder: null,
|
|
128
|
+
partialIndex: false,
|
|
129
|
+
dbDir: null,
|
|
130
|
+
} as unknown as FffState,
|
|
131
|
+
cursorStore: { store: () => "", get: () => undefined } as unknown as CursorStore,
|
|
132
|
+
},
|
|
133
|
+
() => {},
|
|
134
|
+
);
|
|
135
|
+
const theme: ThemeLike = { fg: (_k: string, v: string) => v, bold: (v: string) => v };
|
|
136
|
+
const strip = (s: string) => s.replace(/\u001b\[[0-9;]*m/g, "");
|
|
137
|
+
// edit today shows summary + diff; the ask is that single-step call already covers summary
|
|
138
|
+
// so collapsed result stays compact — this test documents current behavior does NOT duplicate
|
|
139
|
+
const result = {
|
|
140
|
+
content: [{ type: "text", text: "edited" }],
|
|
141
|
+
details: {
|
|
142
|
+
_type: "editInfo",
|
|
143
|
+
filePath: "app.py",
|
|
144
|
+
summary: "-1",
|
|
145
|
+
oldContent: "old line\n",
|
|
146
|
+
newContent: "new line\n",
|
|
147
|
+
language: "python",
|
|
148
|
+
editLine: 1,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
const out =
|
|
152
|
+
registered
|
|
153
|
+
.renderResult?.(result, { isPartial: false }, theme, {
|
|
154
|
+
expanded: false,
|
|
155
|
+
isError: false,
|
|
156
|
+
invalidate: () => {},
|
|
157
|
+
state: {},
|
|
158
|
+
} as unknown as RenderContextLike)
|
|
159
|
+
?.getText() ?? "";
|
|
160
|
+
// collapsed/resting: diff placeholder path covered; when expanded framing should not duplicate header
|
|
161
|
+
expect(strip(out).split("\n").length).toBeGreaterThanOrEqual(1);
|
|
162
|
+
});
|
|
163
|
+
|
|
109
164
|
it("collapses structured errors and restores the exact diagnostic on expansion", () => {
|
|
110
165
|
const registered: { renderResult?: (...args: unknown[]) => MockTextComponent } = {};
|
|
111
166
|
const mockPi: PiPrettyApi = {
|
|
@@ -155,7 +210,7 @@ describe("registerEditTool", () => {
|
|
|
155
210
|
?.getText() ?? "";
|
|
156
211
|
|
|
157
212
|
expect(render({ timer: 1 })).toContain(diagnostic);
|
|
158
|
-
expect(render({ collapsed: true })).toContain("✗
|
|
213
|
+
expect(render({ collapsed: true })).toContain("✗ edit sample.ts · failed");
|
|
159
214
|
expect(render({ collapsed: true }, true)).toContain(diagnostic);
|
|
160
215
|
});
|
|
161
216
|
});
|
package/src/edit.ts
CHANGED
|
@@ -28,6 +28,7 @@ import type {
|
|
|
28
28
|
ToolResultLike,
|
|
29
29
|
} from "@xynogen/pix-pretty/types";
|
|
30
30
|
import {
|
|
31
|
+
dotJoin,
|
|
31
32
|
fillToolBackground,
|
|
32
33
|
getTextContent,
|
|
33
34
|
hideCollapsedToolCall,
|
|
@@ -128,13 +129,32 @@ export function registerEditTool(
|
|
|
128
129
|
const operations = getEditOperations(params);
|
|
129
130
|
const fileLang = lang(fp);
|
|
130
131
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
132
|
+
let result: ToolResultLike;
|
|
133
|
+
try {
|
|
134
|
+
result = (await origEdit.execute(
|
|
135
|
+
tid,
|
|
136
|
+
params as unknown as Parameters<typeof origEdit.execute>[1],
|
|
137
|
+
sig,
|
|
138
|
+
upd,
|
|
139
|
+
toolCtx,
|
|
140
|
+
)) as ToolResultLike;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
143
|
+
if (sig?.aborted || /aborted/i.test(text)) throw error;
|
|
144
|
+
return {
|
|
145
|
+
content: [{ type: "text" as const, text }],
|
|
146
|
+
details: {
|
|
147
|
+
_type: "editInfo" as const,
|
|
148
|
+
summary: "failed",
|
|
149
|
+
editLine: 0,
|
|
150
|
+
oldContent: operations[0]?.oldText ?? "",
|
|
151
|
+
newContent: operations[0]?.newText ?? "",
|
|
152
|
+
language: fileLang,
|
|
153
|
+
filePath: fp,
|
|
154
|
+
},
|
|
155
|
+
isError: true,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
138
158
|
|
|
139
159
|
if (operations.length === 0) return result;
|
|
140
160
|
|
|
@@ -191,11 +211,16 @@ export function registerEditTool(
|
|
|
191
211
|
|
|
192
212
|
const { summary } = summarizeEditOperations(operations);
|
|
193
213
|
const coloredSummary = renderDiffSummary(summary, theme);
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
214
|
+
const paint = (s: string) => theme.fg("dim", s);
|
|
215
|
+
const suffix = dotJoin(
|
|
216
|
+
[
|
|
217
|
+
hdr,
|
|
218
|
+
operations.length > 1 && theme.fg("muted", `${operations.length} edits`),
|
|
219
|
+
coloredSummary,
|
|
220
|
+
],
|
|
221
|
+
paint,
|
|
222
|
+
);
|
|
223
|
+
text.setText(fillToolBackground(suffix));
|
|
199
224
|
return text;
|
|
200
225
|
},
|
|
201
226
|
|
|
@@ -223,7 +248,7 @@ export function registerEditTool(
|
|
|
223
248
|
d?._type === "editInfo"
|
|
224
249
|
? (d.summary as string)
|
|
225
250
|
: d?._type === "multiEditInfo"
|
|
226
|
-
? `${d.editCount} edits
|
|
251
|
+
? dotJoin([`${d.editCount} edits`, String(d.summary)])
|
|
227
252
|
: "edited";
|
|
228
253
|
let filePath = "";
|
|
229
254
|
if (d?._type === "editInfo") filePath = String(d.filePath ?? "");
|
|
@@ -254,10 +279,8 @@ export function registerEditTool(
|
|
|
254
279
|
if (renderCtx.toolCallId) trackInvalidator(renderCtx.toolCallId, renderCtx.invalidate);
|
|
255
280
|
if (renderCtx.state._edk !== key) {
|
|
256
281
|
renderCtx.state._edk = key;
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const summary = renderDiffSummary(String(d.summary), theme);
|
|
260
|
-
renderCtx.state._edt = ` ${summary}${loc}\n${theme.fg("muted", " rendering diff…")}`;
|
|
282
|
+
// ponytail: call already shows `edit <file> <summary>`; don't repeat summary+loc header — gutter carries absolute line
|
|
283
|
+
renderCtx.state._edt = theme.fg("muted", " rendering diff…");
|
|
261
284
|
const dc = resolveDiffColors(theme);
|
|
262
285
|
const diff = parseDiff(
|
|
263
286
|
d.oldContent as string,
|
|
@@ -268,10 +291,7 @@ export function registerEditTool(
|
|
|
268
291
|
renderSplit(diff, d.language as string | undefined, MAX_RENDER_LINES, dc)
|
|
269
292
|
.then((rendered) => {
|
|
270
293
|
if (renderCtx.state._edk !== key) return;
|
|
271
|
-
|
|
272
|
-
(d.editLine as number) > 0 ? ` ${theme.fg("muted", `at line ${d.editLine}`)}` : "";
|
|
273
|
-
const summary = renderDiffSummary(String(d.summary), theme);
|
|
274
|
-
renderCtx.state._edt = ` ${summary}${loc2}\n${rendered}`;
|
|
294
|
+
renderCtx.state._edt = rendered;
|
|
275
295
|
renderCtx.invalidate();
|
|
276
296
|
})
|
|
277
297
|
.catch(() => {
|
|
@@ -290,8 +310,8 @@ export function registerEditTool(
|
|
|
290
310
|
if (renderCtx.toolCallId) trackInvalidator(renderCtx.toolCallId, renderCtx.invalidate);
|
|
291
311
|
if (renderCtx.state._edk !== key) {
|
|
292
312
|
renderCtx.state._edk = key;
|
|
293
|
-
|
|
294
|
-
renderCtx.state._edt =
|
|
313
|
+
// ponytail: call already shows summary; render diffs directly
|
|
314
|
+
renderCtx.state._edt = theme.fg("muted", " rendering diff…");
|
|
295
315
|
const dc = resolveDiffColors(theme);
|
|
296
316
|
Promise.all(
|
|
297
317
|
(
|
|
@@ -309,8 +329,7 @@ export function registerEditTool(
|
|
|
309
329
|
.then((rendered) => {
|
|
310
330
|
if (renderCtx.state._edk !== key) return;
|
|
311
331
|
const body = rendered.join(`\n${theme.fg("muted", " ···")}\n`);
|
|
312
|
-
|
|
313
|
-
renderCtx.state._edt = ` ${theme.fg("muted", `${d.editCount} edits`)} ${summary}\n${body}`;
|
|
332
|
+
renderCtx.state._edt = body;
|
|
314
333
|
renderCtx.invalidate();
|
|
315
334
|
})
|
|
316
335
|
.catch(() => {
|