@xynogen/pix-edit 0.2.17 → 0.2.18
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 +8 -5
- package/skills/pix-edit.md +46 -0
- package/src/edit.ts +113 -168
- package/src/extension.ts +8 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xynogen/pix-edit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.18",
|
|
4
4
|
"description": "Pi tool — precise text replacement edit with diff rendering",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -13,13 +13,16 @@
|
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"src",
|
|
16
|
-
"!src/**/*.test.*",
|
|
17
16
|
"README.md",
|
|
18
|
-
"LICENSE"
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"skills"
|
|
19
19
|
],
|
|
20
20
|
"pi": {
|
|
21
21
|
"extensions": [
|
|
22
22
|
"src/extension.ts"
|
|
23
|
+
],
|
|
24
|
+
"skills": [
|
|
25
|
+
"./skills"
|
|
23
26
|
]
|
|
24
27
|
},
|
|
25
28
|
"keywords": [
|
|
@@ -39,8 +42,8 @@
|
|
|
39
42
|
"access": "public"
|
|
40
43
|
},
|
|
41
44
|
"dependencies": {
|
|
42
|
-
"@xynogen/pix-pretty": "^2.
|
|
43
|
-
"@xynogen/pix-runtime": "^0.
|
|
45
|
+
"@xynogen/pix-pretty": "^2.1.0",
|
|
46
|
+
"@xynogen/pix-runtime": "^0.20.0"
|
|
44
47
|
},
|
|
45
48
|
"peerDependencies": {
|
|
46
49
|
"typebox": "*",
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pix-edit
|
|
3
|
+
description: How to use `edit` without retries — hash anchors from a fresh `read`, one non-overlapping batch per file, and the exact-text fallback. Use before multi-hunk edits, after an edit failed or applied only partly, or when a file may have changed.
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
# edit — hash-anchor edits
|
|
7
|
+
|
|
8
|
+
`edit` was the second most error-prone tool in history (898 errors in 11,315 calls). Nearly all came from four causes below.
|
|
9
|
+
|
|
10
|
+
## Formats
|
|
11
|
+
|
|
12
|
+
Hash anchors (preferred), from the `LINE#HASH` prefixes that `read` returns:
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{ "path": "src/a.ts", "edits": [
|
|
16
|
+
{ "op": "replace", "pos": "12#A1F", "end": "14#C03", "lines": ["new line 12", "new line 13"] },
|
|
17
|
+
{ "op": "insert_after", "pos": "40#9B2", "lines": ["added"] }
|
|
18
|
+
] }
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- `op`: `replace`, `insert_before`, or `insert_after`. `end` is for `replace` only.
|
|
22
|
+
- `lines` holds raw source lines: no `LINE#HASH|` prefix and no newline characters. An empty array deletes.
|
|
23
|
+
|
|
24
|
+
Exact text (fallback only after a hash edit fails): `{ "oldText": "...", "newText": "..." }`. `oldText` must match exactly once.
|
|
25
|
+
|
|
26
|
+
## Avoid the four common failures
|
|
27
|
+
|
|
28
|
+
| Error in history | Count | Fix |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| "Edit without read" | 293 | `read` the region in this session before editing it. |
|
|
31
|
+
| "target not found" / stale anchor | 77+ | The file changed. Use the "Current anchors" from the last edit result, or `read` again. Never guess a hash. |
|
|
32
|
+
| "PARTIAL APPLY" | 100+ | Edits in one call must not overlap and must all target the original file. Split dependent changes into separate calls. |
|
|
33
|
+
| schema: `edits.N` must be object / additional properties / `op` | 59 | Never mix hash and exact-text items in one call. Do not add fields such as `text` or `content`. |
|
|
34
|
+
|
|
35
|
+
## Pattern
|
|
36
|
+
|
|
37
|
+
1. `read` the lines you will change (use `offset`/`limit`, or `ast` `outline` then `symbol` for code).
|
|
38
|
+
2. Put every independent change to that file in one `edit` call.
|
|
39
|
+
3. Use the "Current anchors" in the result for the next edit to the same region.
|
|
40
|
+
4. Read the "LSP after edit" note. Fix new errors before moving on.
|
|
41
|
+
|
|
42
|
+
## Do not
|
|
43
|
+
|
|
44
|
+
- Use `sed -i`, `perl -pi`, or a heredoc in `bash` to change files.
|
|
45
|
+
- Use `write` to change a few lines of an existing file. `write` is for new files and complete rewrites.
|
|
46
|
+
- Retry the same failed edit unchanged. Read the error, then fix the anchor or the format.
|
package/src/edit.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AgentToolUpdateCallback,
|
|
3
|
-
EditToolInput,
|
|
4
|
-
ExtensionContext,
|
|
5
|
-
ToolRenderResultOptions,
|
|
6
|
-
} from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import { resolveBaseBackground } from "@xynogen/pix-pretty/ansi";
|
|
8
1
|
import { MAX_RENDER_LINES } from "@xynogen/pix-pretty/config";
|
|
9
2
|
import type { ToolContext } from "@xynogen/pix-pretty/context";
|
|
10
3
|
import { parseDiff } from "@xynogen/pix-pretty/diff";
|
|
@@ -16,13 +9,12 @@ import {
|
|
|
16
9
|
summarize,
|
|
17
10
|
} from "@xynogen/pix-pretty/diff-render";
|
|
18
11
|
import { lang } from "@xynogen/pix-pretty/lang";
|
|
12
|
+
import { toolCallRenderer, toolCardRenderer } from "@xynogen/pix-pretty/tool-card";
|
|
19
13
|
import type {
|
|
20
14
|
EditOperation,
|
|
21
15
|
EditParams,
|
|
22
16
|
EditRenderState,
|
|
23
17
|
PiPrettyApi,
|
|
24
|
-
RenderContextLike,
|
|
25
|
-
ThemeLike,
|
|
26
18
|
ToolFactory,
|
|
27
19
|
ToolResultLike,
|
|
28
20
|
} from "@xynogen/pix-pretty/types";
|
|
@@ -30,19 +22,12 @@ import {
|
|
|
30
22
|
BODY_PAD,
|
|
31
23
|
dotJoin,
|
|
32
24
|
fillToolBackground,
|
|
33
|
-
frameToolResult,
|
|
34
25
|
getErrorMessage,
|
|
35
|
-
getTextContent,
|
|
36
|
-
hideCollapsedToolCall,
|
|
37
26
|
isTextContent,
|
|
38
|
-
renderCollapsed,
|
|
39
27
|
renderTitle,
|
|
40
|
-
renderToolError,
|
|
41
28
|
setResultDetails,
|
|
42
29
|
termW,
|
|
43
|
-
unframeToolResult,
|
|
44
30
|
} from "@xynogen/pix-pretty/utils";
|
|
45
|
-
import { type CollapseState, tickCollapse } from "@xynogen/pix-runtime/collapse";
|
|
46
31
|
import {
|
|
47
32
|
type EditInput,
|
|
48
33
|
editPath,
|
|
@@ -51,6 +36,11 @@ import {
|
|
|
51
36
|
type HashParams,
|
|
52
37
|
prepareEditArguments,
|
|
53
38
|
} from "@xynogen/pix-runtime/files";
|
|
39
|
+
import type {
|
|
40
|
+
AgentToolUpdateCallback,
|
|
41
|
+
EditToolInput,
|
|
42
|
+
ExtensionContext,
|
|
43
|
+
} from "@xynogen/pix-runtime/host";
|
|
54
44
|
|
|
55
45
|
export {
|
|
56
46
|
editPath,
|
|
@@ -126,7 +116,7 @@ export function registerEditTool(
|
|
|
126
116
|
parameters: editSchema,
|
|
127
117
|
promptSnippet: "Edit with hash anchors first; exact text only after failure",
|
|
128
118
|
promptGuidelines: [
|
|
129
|
-
|
|
119
|
+
'For edit, try hash anchors from read first. Only if the hash-anchor edit fails, fall back to oldText/newText. If the file changed, read the affected region again before using current source text; never guess anchors or overwrite unseen changes. After a failed or partial edit, load read_skills(name="pix-edit", full=true).',
|
|
130
120
|
],
|
|
131
121
|
prepareArguments: prepareEditArguments,
|
|
132
122
|
constrainedSampling: { type: "json_schema", strict: "prefer" },
|
|
@@ -212,71 +202,39 @@ export function registerEditTool(
|
|
|
212
202
|
return result;
|
|
213
203
|
},
|
|
214
204
|
|
|
215
|
-
renderCall(
|
|
216
|
-
|
|
217
|
-
theme
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const fp = args?.path ?? args?.file_path ?? "";
|
|
222
|
-
const operations = getEditOperations(args);
|
|
223
|
-
const text = renderCtx.lastComponent ?? new TextComponent("", 0, 0);
|
|
224
|
-
if (
|
|
225
|
-
hideCollapsedToolCall(renderCtx.state as CollapseState, renderCtx.expanded, (value) =>
|
|
226
|
-
text.setText(value),
|
|
227
|
-
)
|
|
228
|
-
)
|
|
229
|
-
return text;
|
|
230
|
-
const hdr = `${renderTitle(theme, "edit", renderCtx)} ${theme.fg("dim", sp(fp))}`;
|
|
231
|
-
|
|
232
|
-
if (Array.isArray(args.edits) && args.edits.some((edit) => "pos" in edit)) {
|
|
233
|
-
text.setText(
|
|
234
|
-
fillToolBackground(dotJoin([hdr, theme.fg("muted", `${args.edits.length} operations`)])),
|
|
235
|
-
);
|
|
236
|
-
return text;
|
|
237
|
-
}
|
|
238
|
-
if (operations.length === 0) {
|
|
239
|
-
text.setText(fillToolBackground(hdr));
|
|
240
|
-
return text;
|
|
241
|
-
}
|
|
205
|
+
renderCall: toolCallRenderer<EditParams & Partial<HashParams>, EditRenderState>(
|
|
206
|
+
TextComponent,
|
|
207
|
+
(args, theme, renderCtx) => {
|
|
208
|
+
const fp = args?.path ?? args?.file_path ?? "";
|
|
209
|
+
const operations = getEditOperations(args);
|
|
210
|
+
const hdr = `${renderTitle(theme, "edit", renderCtx)} ${theme.fg("dim", sp(fp))}`;
|
|
242
211
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
hdr,
|
|
249
|
-
operations.length > 1 && theme.fg("muted", `${operations.length} edits`),
|
|
250
|
-
theme.fg("muted", "estimate"),
|
|
251
|
-
coloredSummary,
|
|
252
|
-
],
|
|
253
|
-
paint,
|
|
254
|
-
);
|
|
255
|
-
text.setText(fillToolBackground(suffix));
|
|
256
|
-
return text;
|
|
257
|
-
},
|
|
212
|
+
if (Array.isArray(args.edits) && args.edits.some((edit) => "pos" in edit))
|
|
213
|
+
return fillToolBackground(
|
|
214
|
+
dotJoin([hdr, theme.fg("muted", `${args.edits.length} operations`)]),
|
|
215
|
+
);
|
|
216
|
+
if (operations.length === 0) return fillToolBackground(hdr);
|
|
258
217
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
return isPartial ? text : completed();
|
|
275
|
-
}
|
|
218
|
+
const { summary } = summarizeEditOperations(operations);
|
|
219
|
+
const coloredSummary = renderDiffSummary(summary, theme);
|
|
220
|
+
const paint = (s: string) => theme.fg("muted", s);
|
|
221
|
+
const suffix = dotJoin(
|
|
222
|
+
[
|
|
223
|
+
hdr,
|
|
224
|
+
operations.length > 1 && theme.fg("muted", `${operations.length} edits`),
|
|
225
|
+
theme.fg("muted", "estimate"),
|
|
226
|
+
coloredSummary,
|
|
227
|
+
],
|
|
228
|
+
paint,
|
|
229
|
+
);
|
|
230
|
+
return fillToolBackground(suffix);
|
|
231
|
+
},
|
|
232
|
+
),
|
|
276
233
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
234
|
+
renderResult: toolCardRenderer<Record<string, unknown>, EditRenderState>(TextComponent, {
|
|
235
|
+
name: "edit",
|
|
236
|
+
owns: (d) => d?._type === "editInfo" || d?._type === "multiEditInfo",
|
|
237
|
+
collapsed(d) {
|
|
280
238
|
const summary =
|
|
281
239
|
d?._type === "editInfo"
|
|
282
240
|
? (d.summary as string)
|
|
@@ -289,98 +247,85 @@ export function registerEditTool(
|
|
|
289
247
|
const ops = d.ops as Array<Record<string, unknown>> | undefined;
|
|
290
248
|
filePath = String(ops?.[0]?.filePath ?? "");
|
|
291
249
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
renderSplit(diff, d.language as string | undefined, MAX_RENDER_LINES, dc)
|
|
325
|
-
.then((rendered) => {
|
|
326
|
-
if (renderCtx.state._edk !== key) return;
|
|
327
|
-
renderCtx.state._edt = rendered;
|
|
328
|
-
renderCtx.invalidate();
|
|
329
|
-
})
|
|
330
|
-
.catch(() => {
|
|
331
|
-
if (renderCtx.state._edk !== key) return;
|
|
332
|
-
renderCtx.state._edt = `${BODY_PAD}${d.summary}`;
|
|
333
|
-
renderCtx.invalidate();
|
|
334
|
-
});
|
|
250
|
+
return { target: sp(filePath), summary };
|
|
251
|
+
},
|
|
252
|
+
body({ d, result, text, theme, ctx: renderCtx }) {
|
|
253
|
+
// Single edit — full split diff
|
|
254
|
+
if (d?._type === "editInfo") {
|
|
255
|
+
const key = `ed:${diffThemeCacheKey(theme)}:${termW()}:${d.summary}:${(d.oldContent as string).length}:${(d.newContent as string).length}:${d.language ?? ""}`;
|
|
256
|
+
if (renderCtx.toolCallId) trackInvalidator(renderCtx.toolCallId, renderCtx.invalidate);
|
|
257
|
+
if (renderCtx.state._edk !== key) {
|
|
258
|
+
renderCtx.state._edk = key;
|
|
259
|
+
// ponytail: call already shows `edit <file> <summary>`; don't repeat summary+loc header — gutter carries absolute line
|
|
260
|
+
renderCtx.state._edt = `${BODY_PAD}${theme.fg("muted", "rendering diff…")}`;
|
|
261
|
+
const dc = resolveDiffColors(theme);
|
|
262
|
+
const diff = parseDiff(
|
|
263
|
+
d.oldContent as string,
|
|
264
|
+
d.newContent as string,
|
|
265
|
+
3,
|
|
266
|
+
d.editLine as number,
|
|
267
|
+
);
|
|
268
|
+
renderSplit(diff, d.language as string | undefined, MAX_RENDER_LINES, dc)
|
|
269
|
+
.then((rendered) => {
|
|
270
|
+
if (renderCtx.state._edk !== key) return;
|
|
271
|
+
renderCtx.state._edt = rendered;
|
|
272
|
+
renderCtx.invalidate();
|
|
273
|
+
})
|
|
274
|
+
.catch(() => {
|
|
275
|
+
if (renderCtx.state._edk !== key) return;
|
|
276
|
+
renderCtx.state._edt = `${BODY_PAD}${d.summary}`;
|
|
277
|
+
renderCtx.invalidate();
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
text.setText(renderCtx.state._edt ?? `${BODY_PAD}${d.summary}`);
|
|
281
|
+
return undefined;
|
|
335
282
|
}
|
|
336
|
-
text.setText(renderCtx.state._edt ?? `${BODY_PAD}${d.summary}`);
|
|
337
|
-
return isPartial ? text : completed();
|
|
338
|
-
}
|
|
339
283
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
284
|
+
// Multi-edit — stacked diffs
|
|
285
|
+
if (d?._type === "multiEditInfo") {
|
|
286
|
+
const key = `med:${diffThemeCacheKey(theme)}:${termW()}:${d.summary}:${d.editCount}:${d.diffLineCount}`;
|
|
287
|
+
if (renderCtx.toolCallId) trackInvalidator(renderCtx.toolCallId, renderCtx.invalidate);
|
|
288
|
+
if (renderCtx.state._edk !== key) {
|
|
289
|
+
renderCtx.state._edk = key;
|
|
290
|
+
// ponytail: call already shows summary; render diffs directly
|
|
291
|
+
renderCtx.state._edt = `${BODY_PAD}${theme.fg("muted", "rendering diff…")}`;
|
|
292
|
+
const dc = resolveDiffColors(theme);
|
|
293
|
+
Promise.all(
|
|
294
|
+
(
|
|
295
|
+
d.ops as Array<{
|
|
296
|
+
oldContent: string;
|
|
297
|
+
newContent: string;
|
|
298
|
+
language?: string;
|
|
299
|
+
editLine?: number;
|
|
300
|
+
}>
|
|
301
|
+
).map((op) => {
|
|
302
|
+
const diff = parseDiff(op.oldContent, op.newContent, 3, op.editLine ?? 0);
|
|
303
|
+
return renderSplit(diff, op.language, MAX_RENDER_LINES, dc);
|
|
304
|
+
}),
|
|
305
|
+
)
|
|
306
|
+
.then((rendered) => {
|
|
307
|
+
if (renderCtx.state._edk !== key) return;
|
|
308
|
+
const body = rendered.join(`\n${`${BODY_PAD}${theme.fg("muted", "···")}`}\n`);
|
|
309
|
+
renderCtx.state._edt = body;
|
|
310
|
+
renderCtx.invalidate();
|
|
311
|
+
})
|
|
312
|
+
.catch(() => {
|
|
313
|
+
if (renderCtx.state._edk !== key) return;
|
|
314
|
+
renderCtx.state._edt = `${BODY_PAD}${d.editCount} edits ${d.summary}`;
|
|
315
|
+
renderCtx.invalidate();
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
text.setText(renderCtx.state._edt ?? `${BODY_PAD}${d.editCount} edits ${d.summary}`);
|
|
319
|
+
return undefined;
|
|
373
320
|
}
|
|
374
|
-
text.setText(renderCtx.state._edt ?? `${BODY_PAD}${d.editCount} edits ${d.summary}`);
|
|
375
|
-
return isPartial ? text : completed();
|
|
376
|
-
}
|
|
377
321
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
322
|
+
const fallback = result.content?.[0];
|
|
323
|
+
const fallbackText = fallback && isTextContent(fallback) ? fallback.text : "edited";
|
|
324
|
+
text.setText(
|
|
325
|
+
fillToolBackground(`${BODY_PAD}${theme.fg("dim", String(fallbackText).slice(0, 120))}`),
|
|
326
|
+
);
|
|
327
|
+
return undefined;
|
|
328
|
+
},
|
|
329
|
+
}),
|
|
385
330
|
});
|
|
386
331
|
}
|
package/src/extension.ts
CHANGED
|
@@ -1,25 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createEditToolDefinition,
|
|
3
|
-
createEditTool as createEditToolFallback,
|
|
4
|
-
type EditToolInput,
|
|
5
|
-
type ExtensionAPI,
|
|
6
|
-
} from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
8
1
|
import { CursorStore, fffState } from "@xynogen/pix-pretty/fff";
|
|
9
2
|
import { attachResizeListener, trackInvalidator } from "@xynogen/pix-pretty/resize";
|
|
10
|
-
import
|
|
3
|
+
import { Text } from "@xynogen/pix-pretty/tui";
|
|
11
4
|
import { linkedShortPath, viewportTextConstructor } from "@xynogen/pix-pretty/utils";
|
|
12
5
|
import { initHashline } from "@xynogen/pix-runtime/hashline";
|
|
6
|
+
import {
|
|
7
|
+
createEditToolDefinition,
|
|
8
|
+
createEditTool as createEditToolFallback,
|
|
9
|
+
type ExtensionAPI,
|
|
10
|
+
} from "@xynogen/pix-runtime/host";
|
|
13
11
|
import { once } from "@xynogen/pix-runtime/once";
|
|
14
12
|
import { homeDir } from "@xynogen/pix-runtime/paths";
|
|
15
13
|
import { registerEditTool } from "./edit.ts";
|
|
16
14
|
|
|
17
15
|
export default async function pixEditExtension(pi: ExtensionAPI): Promise<void> {
|
|
18
16
|
await initHashline();
|
|
19
|
-
const prettyPi = pi as unknown as PiPrettyApi;
|
|
20
17
|
once(pi, "pix-edit", () => {
|
|
21
|
-
const createEditTool =
|
|
22
|
-
createEditToolFallback) as unknown as ToolFactory<EditToolInput>;
|
|
18
|
+
const createEditTool = createEditToolDefinition ?? createEditToolFallback;
|
|
23
19
|
if (!createEditTool) return;
|
|
24
20
|
|
|
25
21
|
const cwd = process.cwd();
|
|
@@ -28,7 +24,7 @@ export default async function pixEditExtension(pi: ExtensionAPI): Promise<void>
|
|
|
28
24
|
attachResizeListener();
|
|
29
25
|
|
|
30
26
|
registerEditTool(
|
|
31
|
-
|
|
27
|
+
pi,
|
|
32
28
|
createEditTool,
|
|
33
29
|
{
|
|
34
30
|
cwd,
|