@xynogen/pix-edit 0.1.14 → 0.1.16
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 +2 -2
- package/package.json +1 -1
- package/src/edit.test.ts +117 -5
- package/src/edit.ts +21 -4
package/README.md
CHANGED
|
@@ -8,13 +8,13 @@ Replaces Pi's default `edit` tool with an enhanced version that renders a side-b
|
|
|
8
8
|
|
|
9
9
|
## Auto-collapse
|
|
10
10
|
|
|
11
|
-
After a configurable delay (default 10 seconds) the diff
|
|
11
|
+
After a configurable delay (default 10 seconds), a completed diff collapses to a one-line status row. Structured failures use a compact `✗` row after the delay. Expanding restores the existing bounded diff or exact diagnostic without restarting the elapsed timer. This is controlled via the `collapse` section of `~/.pi/agent/pix.json`:
|
|
12
12
|
|
|
13
13
|
```jsonc
|
|
14
14
|
{
|
|
15
15
|
"collapse": {
|
|
16
16
|
"enabled": true,
|
|
17
|
-
"
|
|
17
|
+
"delaySec": 10,
|
|
18
18
|
"tools": { "edit": true }
|
|
19
19
|
}
|
|
20
20
|
}
|
package/package.json
CHANGED
package/src/edit.test.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from "bun:test";
|
|
2
2
|
import type { CursorStore, FffState } from "@xynogen/pix-pretty/fff";
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
PiPrettyApi,
|
|
5
|
+
RenderContextLike,
|
|
6
|
+
TextComponentCtor,
|
|
7
|
+
ThemeLike,
|
|
8
|
+
} from "@xynogen/pix-pretty/types";
|
|
4
9
|
import { getEditOperations, registerEditTool, summarizeEditOperations } from "./edit";
|
|
5
10
|
|
|
6
11
|
class MockTextComponent {
|
|
@@ -14,11 +19,11 @@ class MockTextComponent {
|
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
describe("registerEditTool", () => {
|
|
17
|
-
it("registers a
|
|
18
|
-
const tools: string
|
|
22
|
+
it("registers a self-rendered edit tool", () => {
|
|
23
|
+
const tools: Array<{ name: string; renderShell?: string }> = [];
|
|
19
24
|
const mockPi: PiPrettyApi = {
|
|
20
25
|
registerTool(t: unknown) {
|
|
21
|
-
tools.push(
|
|
26
|
+
tools.push(t as { name: string; renderShell?: string });
|
|
22
27
|
},
|
|
23
28
|
registerCommand() {},
|
|
24
29
|
on() {},
|
|
@@ -44,7 +49,114 @@ describe("registerEditTool", () => {
|
|
|
44
49
|
},
|
|
45
50
|
(_id: string, _inv: () => void) => {},
|
|
46
51
|
);
|
|
47
|
-
expect(tools).
|
|
52
|
+
expect(tools).toHaveLength(1);
|
|
53
|
+
expect(tools[0]?.name).toBe("edit");
|
|
54
|
+
expect(tools[0]?.renderShell).toBe("self");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("restores the bounded diff when an elapsed card is expanded", () => {
|
|
58
|
+
const registered: { renderResult?: (...args: unknown[]) => MockTextComponent } = {};
|
|
59
|
+
const mockPi: PiPrettyApi = {
|
|
60
|
+
registerTool(tool: unknown) {
|
|
61
|
+
Object.assign(registered, tool);
|
|
62
|
+
},
|
|
63
|
+
registerCommand() {},
|
|
64
|
+
on() {},
|
|
65
|
+
};
|
|
66
|
+
registerEditTool(
|
|
67
|
+
mockPi,
|
|
68
|
+
() => ({ execute: async () => ({ content: [], details: undefined }) }),
|
|
69
|
+
{
|
|
70
|
+
cwd: process.cwd(),
|
|
71
|
+
sp: (p: string) => p,
|
|
72
|
+
TextComponent: MockTextComponent as unknown as TextComponentCtor,
|
|
73
|
+
fffState: { module: null, finder: null, partialIndex: false, dbDir: null },
|
|
74
|
+
cursorStore: { store: () => "", get: () => undefined } as unknown as CursorStore,
|
|
75
|
+
},
|
|
76
|
+
() => {},
|
|
77
|
+
);
|
|
78
|
+
const theme: ThemeLike = {
|
|
79
|
+
fg: (_key: string, value: string) => value,
|
|
80
|
+
bold: (value: string) => value,
|
|
81
|
+
};
|
|
82
|
+
const result = registered.renderResult?.(
|
|
83
|
+
{
|
|
84
|
+
content: [{ type: "text", text: "edited" }],
|
|
85
|
+
details: {
|
|
86
|
+
_type: "editInfo",
|
|
87
|
+
filePath: "sample.ts",
|
|
88
|
+
summary: "+1 -1",
|
|
89
|
+
oldContent: "old",
|
|
90
|
+
newContent: "new",
|
|
91
|
+
language: "typescript",
|
|
92
|
+
editLine: 1,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
undefined,
|
|
96
|
+
theme,
|
|
97
|
+
{
|
|
98
|
+
expanded: true,
|
|
99
|
+
isError: false,
|
|
100
|
+
invalidate: () => {},
|
|
101
|
+
state: { collapsed: true },
|
|
102
|
+
} as unknown as RenderContextLike,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
expect(result?.getText()).toContain("rendering diff");
|
|
106
|
+
expect(result?.getText()).not.toContain("✓ edit");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("collapses structured errors and restores the exact diagnostic on expansion", () => {
|
|
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: { module: null, finder: null, partialIndex: false, dbDir: null },
|
|
126
|
+
cursorStore: { store: () => "", get: () => undefined } as unknown as CursorStore,
|
|
127
|
+
},
|
|
128
|
+
() => {},
|
|
129
|
+
);
|
|
130
|
+
const theme: ThemeLike = {
|
|
131
|
+
fg: (_key: string, value: string) => value,
|
|
132
|
+
bold: (value: string) => value,
|
|
133
|
+
};
|
|
134
|
+
const diagnostic = "oldText was not found in sample.ts";
|
|
135
|
+
const result = {
|
|
136
|
+
content: [{ type: "text", text: diagnostic }],
|
|
137
|
+
details: {
|
|
138
|
+
_type: "editInfo",
|
|
139
|
+
filePath: "sample.ts",
|
|
140
|
+
summary: "+1 -1",
|
|
141
|
+
oldContent: "old",
|
|
142
|
+
newContent: "new",
|
|
143
|
+
language: "typescript",
|
|
144
|
+
editLine: 0,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
const render = (state: Record<string, unknown>, expanded = false) =>
|
|
148
|
+
registered
|
|
149
|
+
.renderResult?.(result, { isPartial: false }, theme, {
|
|
150
|
+
expanded,
|
|
151
|
+
isError: true,
|
|
152
|
+
invalidate: () => {},
|
|
153
|
+
state,
|
|
154
|
+
} as unknown as RenderContextLike)
|
|
155
|
+
?.getText() ?? "";
|
|
156
|
+
|
|
157
|
+
expect(render({ timer: 1 })).toContain(diagnostic);
|
|
158
|
+
expect(render({ collapsed: true })).toContain("✗ edit sample.ts · failed");
|
|
159
|
+
expect(render({ collapsed: true }, true)).toContain(diagnostic);
|
|
48
160
|
});
|
|
49
161
|
});
|
|
50
162
|
|
package/src/edit.ts
CHANGED
|
@@ -114,6 +114,7 @@ export function registerEditTool(
|
|
|
114
114
|
pi.registerTool({
|
|
115
115
|
...origEdit,
|
|
116
116
|
name: "edit",
|
|
117
|
+
renderShell: "self",
|
|
117
118
|
|
|
118
119
|
async execute(
|
|
119
120
|
tid: string,
|
|
@@ -199,15 +200,18 @@ export function registerEditTool(
|
|
|
199
200
|
renderCtx: RenderContextLike<EditRenderState>,
|
|
200
201
|
) {
|
|
201
202
|
const text = renderCtx.lastComponent ?? new TextComponent("", 0, 0);
|
|
202
|
-
|
|
203
|
+
const d = result.details as Record<string, unknown> | undefined;
|
|
204
|
+
const isPartial = _opt?.isPartial === true;
|
|
205
|
+
const structuredError =
|
|
206
|
+
renderCtx.isError && (d?._type === "editInfo" || d?._type === "multiEditInfo");
|
|
207
|
+
if (renderCtx.isError && (!structuredError || isPartial)) {
|
|
203
208
|
text.setText(renderToolError(getTextContent(result) || "Error", theme));
|
|
204
209
|
return text;
|
|
205
210
|
}
|
|
206
|
-
const d = result.details as Record<string, unknown> | undefined;
|
|
207
211
|
|
|
208
212
|
// Auto-collapse: show summary line after delay
|
|
209
213
|
const cs = renderCtx.state as CollapseState;
|
|
210
|
-
if (tickCollapse("edit", cs, renderCtx.invalidate)) {
|
|
214
|
+
if (!isPartial && tickCollapse("edit", cs, renderCtx.invalidate, renderCtx.expanded)) {
|
|
211
215
|
const summary =
|
|
212
216
|
d?._type === "editInfo"
|
|
213
217
|
? (d.summary as string)
|
|
@@ -220,7 +224,20 @@ export function registerEditTool(
|
|
|
220
224
|
const ops = d.ops as Array<Record<string, unknown>> | undefined;
|
|
221
225
|
filePath = String(ops?.[0]?.filePath ?? "");
|
|
222
226
|
}
|
|
223
|
-
text.setText(
|
|
227
|
+
text.setText(
|
|
228
|
+
renderCollapsedToolRow(
|
|
229
|
+
theme,
|
|
230
|
+
"edit",
|
|
231
|
+
sp(filePath),
|
|
232
|
+
renderCtx.isError ? "failed" : summary,
|
|
233
|
+
renderCtx.isError ? "error" : "success",
|
|
234
|
+
),
|
|
235
|
+
);
|
|
236
|
+
return text;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (renderCtx.isError) {
|
|
240
|
+
text.setText(renderToolError(getTextContent(result) || "Error", theme));
|
|
224
241
|
return text;
|
|
225
242
|
}
|
|
226
243
|
|