@xynogen/pix-edit 0.1.6 → 0.1.9
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 +2 -2
- package/src/edit.test.ts +25 -7
- package/src/edit.ts +25 -12
- package/src/once.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xynogen/pix-edit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Pi tool — precise text replacement edit with diff rendering",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@xynogen/pix-pretty": "
|
|
37
|
+
"@xynogen/pix-pretty": "^1.7.9"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@earendil-works/pi-coding-agent": "*",
|
package/src/edit.test.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import type { CursorStore, FffState } from "@xynogen/pix-pretty/fff";
|
|
3
|
+
import type { PiPrettyApi, TextComponentCtor } from "@xynogen/pix-pretty/types";
|
|
2
4
|
import {
|
|
3
5
|
getEditOperations,
|
|
4
6
|
registerEditTool,
|
|
@@ -18,15 +20,31 @@ class MockTextComponent {
|
|
|
18
20
|
describe("registerEditTool", () => {
|
|
19
21
|
it("registers a tool named 'edit'", () => {
|
|
20
22
|
const tools: string[] = [];
|
|
23
|
+
const mockPi: PiPrettyApi = {
|
|
24
|
+
registerTool(t: unknown) {
|
|
25
|
+
tools.push((t as { name: string }).name);
|
|
26
|
+
},
|
|
27
|
+
registerCommand() {},
|
|
28
|
+
on() {},
|
|
29
|
+
};
|
|
30
|
+
|
|
21
31
|
registerEditTool(
|
|
22
|
-
|
|
23
|
-
() => ({ execute: async () => ({ content: [] }) })
|
|
32
|
+
mockPi,
|
|
33
|
+
() => ({ execute: async () => ({ content: [], details: undefined }) }),
|
|
24
34
|
{
|
|
25
35
|
cwd: process.cwd(),
|
|
26
36
|
sp: (p: string) => p,
|
|
27
|
-
TextComponent: MockTextComponent as
|
|
28
|
-
fffState: {
|
|
29
|
-
|
|
37
|
+
TextComponent: MockTextComponent as unknown as TextComponentCtor,
|
|
38
|
+
fffState: {
|
|
39
|
+
module: null,
|
|
40
|
+
finder: null,
|
|
41
|
+
partialIndex: false,
|
|
42
|
+
dbDir: null,
|
|
43
|
+
} satisfies FffState,
|
|
44
|
+
cursorStore: {
|
|
45
|
+
store: () => "",
|
|
46
|
+
get: () => undefined,
|
|
47
|
+
} as unknown as CursorStore,
|
|
30
48
|
},
|
|
31
49
|
(_id: string, _inv: () => void) => {},
|
|
32
50
|
);
|
|
@@ -39,7 +57,7 @@ describe("getEditOperations", () => {
|
|
|
39
57
|
const ops = getEditOperations({
|
|
40
58
|
path: "f.ts",
|
|
41
59
|
edits: [{ oldText: "a", newText: "b" }],
|
|
42
|
-
}
|
|
60
|
+
});
|
|
43
61
|
expect(ops).toEqual([{ oldText: "a", newText: "b" }]);
|
|
44
62
|
});
|
|
45
63
|
|
|
@@ -47,7 +65,7 @@ describe("getEditOperations", () => {
|
|
|
47
65
|
const ops = getEditOperations({
|
|
48
66
|
path: "f.ts",
|
|
49
67
|
edits: [{ oldText: "x", newText: "x" }],
|
|
50
|
-
}
|
|
68
|
+
});
|
|
51
69
|
expect(ops).toHaveLength(0);
|
|
52
70
|
});
|
|
53
71
|
});
|
package/src/edit.ts
CHANGED
|
@@ -71,6 +71,20 @@ export function getEditOperations(input: EditParams): EditOperation[] {
|
|
|
71
71
|
return oldText && oldText !== newText ? [{ oldText, newText }] : [];
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
// 1-based file line where `needle` begins post-edit, for absolute diff gutters.
|
|
75
|
+
// 0 = file unreadable or needle not found (renderer falls back to relative).
|
|
76
|
+
// Duplicate identical edits collapse to the first match — acceptable.
|
|
77
|
+
function opEditLine(filePath: string, needle: string): number {
|
|
78
|
+
try {
|
|
79
|
+
if (!filePath || !existsSync(filePath)) return 0;
|
|
80
|
+
const f = readFileSync(filePath, "utf-8");
|
|
81
|
+
const idx = f.indexOf(needle);
|
|
82
|
+
return idx >= 0 ? f.slice(0, idx).split("\n").length : 0;
|
|
83
|
+
} catch {
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
74
88
|
export function summarizeEditOperations(operations: EditOperation[]) {
|
|
75
89
|
const diffs = operations.map((e) => parseDiff(e.oldText, e.newText));
|
|
76
90
|
const totalAdded = diffs.reduce((sum, d) => sum + d.added, 0);
|
|
@@ -122,20 +136,10 @@ export function registerEditTool(
|
|
|
122
136
|
const { diffs, summary } = summarizeEditOperations(operations);
|
|
123
137
|
|
|
124
138
|
if (operations.length === 1) {
|
|
125
|
-
let editLine = 0;
|
|
126
|
-
try {
|
|
127
|
-
if (fp && existsSync(fp)) {
|
|
128
|
-
const f = readFileSync(fp, "utf-8");
|
|
129
|
-
const idx = f.indexOf(operations[0].newText);
|
|
130
|
-
if (idx >= 0) editLine = f.slice(0, idx).split("\n").length;
|
|
131
|
-
}
|
|
132
|
-
} catch {
|
|
133
|
-
editLine = 0;
|
|
134
|
-
}
|
|
135
139
|
setResultDetails(result, {
|
|
136
140
|
_type: "editInfo",
|
|
137
141
|
summary,
|
|
138
|
-
editLine,
|
|
142
|
+
editLine: opEditLine(fp, operations[0].newText),
|
|
139
143
|
oldContent: operations[0].oldText,
|
|
140
144
|
newContent: operations[0].newText,
|
|
141
145
|
language: fileLang,
|
|
@@ -154,6 +158,7 @@ export function registerEditTool(
|
|
|
154
158
|
newContent: op.newText,
|
|
155
159
|
language: fileLang,
|
|
156
160
|
filePath: fp,
|
|
161
|
+
editLine: opEditLine(fp, op.newText),
|
|
157
162
|
})),
|
|
158
163
|
});
|
|
159
164
|
return result;
|
|
@@ -212,6 +217,8 @@ export function registerEditTool(
|
|
|
212
217
|
const diff = parseDiff(
|
|
213
218
|
d.oldContent as string,
|
|
214
219
|
d.newContent as string,
|
|
220
|
+
3,
|
|
221
|
+
d.editLine as number,
|
|
215
222
|
);
|
|
216
223
|
renderSplit(
|
|
217
224
|
diff,
|
|
@@ -253,9 +260,15 @@ export function registerEditTool(
|
|
|
253
260
|
oldContent: string;
|
|
254
261
|
newContent: string;
|
|
255
262
|
language?: string;
|
|
263
|
+
editLine?: number;
|
|
256
264
|
}>
|
|
257
265
|
).map((op) => {
|
|
258
|
-
const diff = parseDiff(
|
|
266
|
+
const diff = parseDiff(
|
|
267
|
+
op.oldContent,
|
|
268
|
+
op.newContent,
|
|
269
|
+
3,
|
|
270
|
+
op.editLine ?? 0,
|
|
271
|
+
);
|
|
259
272
|
return renderSplit(diff, op.language, MAX_RENDER_LINES, dc);
|
|
260
273
|
}),
|
|
261
274
|
)
|
package/src/once.ts
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
*/
|
|
15
15
|
export function once(pi: object, key: string, fn: () => void): void {
|
|
16
16
|
const g = globalThis as { __pixOnce?: WeakMap<object, Set<string>> };
|
|
17
|
-
|
|
17
|
+
if (!g.__pixOnce) g.__pixOnce = new WeakMap<object, Set<string>>();
|
|
18
|
+
const registry = g.__pixOnce;
|
|
18
19
|
let loaded = registry.get(pi);
|
|
19
20
|
if (!loaded) {
|
|
20
21
|
loaded = new Set<string>();
|