pi-hashline-edit-pro 4.3.9 → 4.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/README.md +5 -7
- package/index.ts +1 -3
- package/package.json +1 -1
- package/prompts/insert.md +1 -1
- package/prompts/replace-guidelines.md +0 -1
- package/src/batch.ts +6 -49
- package/src/commit.ts +1 -30
- package/src/config-ui.ts +1 -2
- package/src/config.ts +0 -24
- package/src/constants.ts +0 -2
- package/src/edit-common.ts +1 -18
- package/src/hashline/apply.ts +4 -69
- package/src/hashline/index.ts +0 -3
- package/src/hashline/resolve.ts +4 -159
- package/src/insert.ts +0 -4
- package/src/replace-diff.ts +7 -44
- package/src/replace-render.ts +0 -2
- package/src/replace-response.ts +5 -86
- package/src/replace.ts +6 -26
package/src/hashline/apply.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { abortIf,
|
|
1
|
+
import { abortIf, splitLines } from "../utils";
|
|
2
2
|
import {
|
|
3
3
|
valEdit,
|
|
4
4
|
stripBarePrefixes,
|
|
@@ -11,8 +11,6 @@ import {
|
|
|
11
11
|
type RHEdit,
|
|
12
12
|
type NEdit,
|
|
13
13
|
type HEdit,
|
|
14
|
-
type AutoFix,
|
|
15
|
-
type BDup,
|
|
16
14
|
} from "./resolve";
|
|
17
15
|
|
|
18
16
|
type LIdx = {
|
|
@@ -150,19 +148,9 @@ function assemble(
|
|
|
150
148
|
export interface PlannedEdit {
|
|
151
149
|
resolved: RHEdit;
|
|
152
150
|
warnings: string[];
|
|
153
|
-
autoFixes?: AutoFix[];
|
|
154
151
|
}
|
|
155
152
|
|
|
156
153
|
|
|
157
|
-
function throwStrictBoundaryDedup(edit: HEdit, boundaryDups: BDup[], resolved: RHEdit): never {
|
|
158
|
-
const startLine = resolved.hash_bounds[0].line;
|
|
159
|
-
const endLine = resolved.hash_bounds[1].line;
|
|
160
|
-
const range = startLine === endLine ? `line ${startLine}` : `lines ${startLine}-${endLine}`;
|
|
161
|
-
const seen = new Set<number>();
|
|
162
|
-
for (const dup of boundaryDups) seen.add(dup.replacementLineIndex);
|
|
163
|
-
const removed = [...seen].sort((a, b) => a - b).map((index) => `replacement_lines line ${index + 1}: ${clipLine(edit.content_lines[index] ?? "")}`);
|
|
164
|
-
throw new Error(`[E_BOUNDARY_STRICT] Strict boundary dedup rejects this edit: ${seen.size} replacement line(s) re-include edge lines (${range}). ${removed.join("; ")}. Resend without those lines.`);
|
|
165
|
-
}
|
|
166
154
|
export function planEdit(
|
|
167
155
|
content: string,
|
|
168
156
|
edit: HEdit,
|
|
@@ -170,8 +158,6 @@ export function planEdit(
|
|
|
170
158
|
options?: {
|
|
171
159
|
filePath?: string;
|
|
172
160
|
servedHashes?: ReadonlyMap<string, string>;
|
|
173
|
-
skipBoundaryDedup?: boolean;
|
|
174
|
-
strictBoundaryDedup?: boolean;
|
|
175
161
|
signal?: AbortSignal;
|
|
176
162
|
baseFileLines?: string[];
|
|
177
163
|
},
|
|
@@ -189,7 +175,7 @@ export function planEdit(
|
|
|
189
175
|
warnings,
|
|
190
176
|
);
|
|
191
177
|
|
|
192
|
-
const { resolved: initialResolved, mismatches
|
|
178
|
+
const { resolved: initialResolved, mismatches } = valEdit(
|
|
193
179
|
prefixFixed,
|
|
194
180
|
lineIndex.fileLines,
|
|
195
181
|
fileHashes,
|
|
@@ -208,51 +194,7 @@ export function planEdit(
|
|
|
208
194
|
|
|
209
195
|
warnUnicodeEsc(prefixFixed, warnings);
|
|
210
196
|
|
|
211
|
-
|
|
212
|
-
throwStrictBoundaryDedup(prefixFixed, boundaryDups, initialResolved);
|
|
213
|
-
}
|
|
214
|
-
let resolved = initialResolved;
|
|
215
|
-
let autoFixes: AutoFix[] | undefined;
|
|
216
|
-
if (boundaryDups.length > 0 && !(options?.skipBoundaryDedup === true)) {
|
|
217
|
-
autoFixes = [];
|
|
218
|
-
const correctedEdit: HEdit = {
|
|
219
|
-
...prefixFixed,
|
|
220
|
-
content_lines: [...prefixFixed.content_lines],
|
|
221
|
-
};
|
|
222
|
-
const seen = new Set<number>();
|
|
223
|
-
const uniqueDups: BDup[] = [];
|
|
224
|
-
for (const dup of boundaryDups) {
|
|
225
|
-
if (seen.has(dup.replacementLineIndex)) continue;
|
|
226
|
-
seen.add(dup.replacementLineIndex);
|
|
227
|
-
uniqueDups.push(dup);
|
|
228
|
-
}
|
|
229
|
-
const dupsByIndex = uniqueDups.sort(
|
|
230
|
-
(a, b) => b.replacementLineIndex - a.replacementLineIndex,
|
|
231
|
-
);
|
|
232
|
-
for (const dup of dupsByIndex) {
|
|
233
|
-
const idx = dup.replacementLineIndex;
|
|
234
|
-
if (idx < 0 || idx >= correctedEdit.content_lines.length) continue;
|
|
235
|
-
const removed = correctedEdit.content_lines.splice(idx, 1)[0];
|
|
236
|
-
autoFixes.push({ kind: dup.kind, removedLine: removed, removedLineIndex: idx });
|
|
237
|
-
}
|
|
238
|
-
const correctedResult = valEdit(
|
|
239
|
-
correctedEdit,
|
|
240
|
-
lineIndex.fileLines,
|
|
241
|
-
fileHashes,
|
|
242
|
-
warnings,
|
|
243
|
-
signal,
|
|
244
|
-
);
|
|
245
|
-
if (correctedResult.mismatches.length || !correctedResult.resolved) {
|
|
246
|
-
const feedback = fmtMismatchWithHashes(
|
|
247
|
-
correctedResult.mismatches,
|
|
248
|
-
lineIndex.fileLines,
|
|
249
|
-
fileHashes,
|
|
250
|
-
options?.filePath,
|
|
251
|
-
);
|
|
252
|
-
throw new AnchorMismatchError(feedback.text, feedback.hashes, feedback.servedMap);
|
|
253
|
-
}
|
|
254
|
-
resolved = correctedResult.resolved;
|
|
255
|
-
}
|
|
197
|
+
const resolved = initialResolved;
|
|
256
198
|
|
|
257
199
|
if (options?.servedHashes) {
|
|
258
200
|
abortIf(signal);
|
|
@@ -262,7 +204,6 @@ export function planEdit(
|
|
|
262
204
|
return {
|
|
263
205
|
resolved,
|
|
264
206
|
warnings,
|
|
265
|
-
...(autoFixes ? { autoFixes } : {}),
|
|
266
207
|
};
|
|
267
208
|
}
|
|
268
209
|
export function applyEdit(
|
|
@@ -272,25 +213,21 @@ export function applyEdit(
|
|
|
272
213
|
precomputedHashes?: string[],
|
|
273
214
|
filePath?: string,
|
|
274
215
|
servedHashes?: ReadonlyMap<string, string>,
|
|
275
|
-
skipBoundaryDedup?: boolean,
|
|
276
|
-
strictBoundaryDedup?: boolean,
|
|
277
216
|
): {
|
|
278
217
|
content: string;
|
|
279
218
|
firstChangedLine: number | undefined;
|
|
280
219
|
lastChangedLine: number | undefined;
|
|
281
220
|
warnings?: string[];
|
|
282
221
|
noopEdit?: NEdit;
|
|
283
|
-
autoFixes?: AutoFix[];
|
|
284
222
|
} {
|
|
285
223
|
abortIf(signal);
|
|
286
224
|
if (precomputedHashes === undefined) {
|
|
287
225
|
throw new Error("[E_BAD_SHAPE] applyEdit requires the file's allocated anchors; derive them via lineHashes(content, path) first.");
|
|
288
226
|
}
|
|
289
|
-
const planned = planEdit(content, edit, precomputedHashes, { filePath, servedHashes,
|
|
227
|
+
const planned = planEdit(content, edit, precomputedHashes, { filePath, servedHashes, signal });
|
|
290
228
|
const lineIndex = buildIdx(content);
|
|
291
229
|
const warnings = planned.warnings;
|
|
292
230
|
const resolved = planned.resolved;
|
|
293
|
-
const autoFixes = planned.autoFixes;
|
|
294
231
|
const spanResult = resToSpan(resolved, content, lineIndex);
|
|
295
232
|
if (spanResult.kind === "noop") {
|
|
296
233
|
return {
|
|
@@ -298,7 +235,6 @@ export function applyEdit(
|
|
|
298
235
|
firstChangedLine: undefined,
|
|
299
236
|
lastChangedLine: undefined,
|
|
300
237
|
...(warnings.length ? { warnings } : {}),
|
|
301
|
-
...(autoFixes ? { autoFixes } : {}),
|
|
302
238
|
noopEdit: { loc: spanResult.loc, currentContent: spanResult.currentContent },
|
|
303
239
|
};
|
|
304
240
|
}
|
|
@@ -312,7 +248,6 @@ export function applyEdit(
|
|
|
312
248
|
firstChangedLine: range?.firstChangedLine,
|
|
313
249
|
lastChangedLine: range?.lastChangedLine,
|
|
314
250
|
...(warnings.length ? { warnings } : {}),
|
|
315
|
-
...(autoFixes ? { autoFixes } : {}),
|
|
316
251
|
};
|
|
317
252
|
}
|
|
318
253
|
|
package/src/hashline/index.ts
CHANGED
|
@@ -26,8 +26,6 @@ export {
|
|
|
26
26
|
type RHEdit,
|
|
27
27
|
type HTEdit,
|
|
28
28
|
type NEdit,
|
|
29
|
-
type BDup,
|
|
30
|
-
type AutoFix,
|
|
31
29
|
resEdit,
|
|
32
30
|
stripAnchorRow,
|
|
33
31
|
resolveAnchorLine,
|
|
@@ -35,7 +33,6 @@ export {
|
|
|
35
33
|
stripBarePrefixes,
|
|
36
34
|
stripDiffPrefixes,
|
|
37
35
|
swapReversedRanges,
|
|
38
|
-
findNewEdge,
|
|
39
36
|
assertRangeServed,
|
|
40
37
|
RangeStaleError,
|
|
41
38
|
AnchorMismatchError,
|
package/src/hashline/resolve.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { abortIf, rejectUnknownFields,
|
|
1
|
+
import { abortIf, rejectUnknownFields, clipLine, decodeStringArray, assertNoNul } from "../utils";
|
|
2
2
|
import { parseHashRef, parseText, type Anchor } from "./parse";
|
|
3
|
-
import { HASH_SEP, stripRowPrefix,
|
|
3
|
+
import { HASH_SEP, stripRowPrefix, lineChecksum, type RowPrefixKind } from "./hash";
|
|
4
4
|
import { HASH_RUN } from "./alphabet";
|
|
5
5
|
import { NEW_CONTENT_NOT_ARRAY_MSG, MAX_RANGE_STALE_LINES } from "../constants";
|
|
6
6
|
|
|
@@ -21,17 +21,6 @@ interface HMismatch {
|
|
|
21
21
|
context?: RAnchor;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export interface BDup {
|
|
25
|
-
kind: "trailing" | "leading" | "first-new-after" | "last-new-before";
|
|
26
|
-
replacementLineIndex: number;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface AutoFix {
|
|
30
|
-
kind: "trailing" | "leading" | "first-new-after" | "last-new-before";
|
|
31
|
-
removedLine: string;
|
|
32
|
-
removedLineIndex: number;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
24
|
export interface NEdit {
|
|
36
25
|
loc: string;
|
|
37
26
|
currentContent: string;
|
|
@@ -240,147 +229,15 @@ export function swapReversedRanges(
|
|
|
240
229
|
return { ...edit, hash_bounds: [endRef, startRef] as [Anchor, Anchor] };
|
|
241
230
|
}
|
|
242
231
|
|
|
243
|
-
function trailingDups(
|
|
244
|
-
contentLines: string[],
|
|
245
|
-
fileLines: string[],
|
|
246
|
-
endLine: number,
|
|
247
|
-
): BDup[] {
|
|
248
|
-
const start = lastNonEmptyIndex(contentLines);
|
|
249
|
-
if (start < 0) return [];
|
|
250
|
-
const dups: BDup[] = [];
|
|
251
|
-
const maxK = Math.min(start + 1, fileLines.length - endLine);
|
|
252
|
-
for (let k = 0; k < maxK; k++) {
|
|
253
|
-
if (contentLines[start - k] !== fileLines[endLine + k]) break;
|
|
254
|
-
dups.push({ kind: "trailing", replacementLineIndex: start - k });
|
|
255
|
-
}
|
|
256
|
-
return dups;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function leadingDups(
|
|
260
|
-
contentLines: string[],
|
|
261
|
-
fileLines: string[],
|
|
262
|
-
startLine: number,
|
|
263
|
-
): BDup[] {
|
|
264
|
-
const start = firstNonEmptyIndex(contentLines);
|
|
265
|
-
if (start < 0) return [];
|
|
266
|
-
const dups: BDup[] = [];
|
|
267
|
-
const maxK = Math.min(contentLines.length - start, startLine - 1);
|
|
268
|
-
for (let k = 0; k < maxK; k++) {
|
|
269
|
-
if (contentLines[start + k] !== fileLines[startLine - 2 - k]) break;
|
|
270
|
-
dups.push({ kind: "leading", replacementLineIndex: start + k });
|
|
271
|
-
}
|
|
272
|
-
return dups;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function sectionIsUnique(
|
|
276
|
-
canonLines: string[],
|
|
277
|
-
start: number,
|
|
278
|
-
length: number,
|
|
279
|
-
): boolean {
|
|
280
|
-
let count = 0;
|
|
281
|
-
for (let i = 0; i + length <= canonLines.length; i++) {
|
|
282
|
-
let k = 0;
|
|
283
|
-
while (k < length && canonLines[i + k] === canonLines[start + k]) k++;
|
|
284
|
-
if (k < length) continue;
|
|
285
|
-
count++;
|
|
286
|
-
if (count > 1) return false;
|
|
287
|
-
}
|
|
288
|
-
return true;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function firstNewAfterDups(
|
|
292
|
-
contentLines: string[],
|
|
293
|
-
rangeLines: string[],
|
|
294
|
-
canonLines: string[],
|
|
295
|
-
endLine: number,
|
|
296
|
-
): BDup[] {
|
|
297
|
-
const firstNew = findNewEdge(contentLines, rangeLines, false);
|
|
298
|
-
if (!firstNew) return [];
|
|
299
|
-
const maxK = Math.min(contentLines.length - firstNew.index, canonLines.length - endLine);
|
|
300
|
-
let runLen = 0;
|
|
301
|
-
while (
|
|
302
|
-
runLen < maxK &&
|
|
303
|
-
canonLines[endLine + runLen]!.length > 0 &&
|
|
304
|
-
canon(contentLines[firstNew.index + runLen]!) === canonLines[endLine + runLen]!
|
|
305
|
-
) {
|
|
306
|
-
runLen++;
|
|
307
|
-
}
|
|
308
|
-
if (runLen === 0 || !sectionIsUnique(canonLines, endLine, runLen)) return [];
|
|
309
|
-
const dups: BDup[] = [];
|
|
310
|
-
for (let k = 0; k < runLen; k++) {
|
|
311
|
-
dups.push({ kind: "first-new-after", replacementLineIndex: firstNew.index + k });
|
|
312
|
-
}
|
|
313
|
-
return dups;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function lastNewBeforeDups(
|
|
317
|
-
contentLines: string[],
|
|
318
|
-
rangeLines: string[],
|
|
319
|
-
canonLines: string[],
|
|
320
|
-
startLine: number,
|
|
321
|
-
): BDup[] {
|
|
322
|
-
const lastNew = findNewEdge(contentLines, rangeLines, true);
|
|
323
|
-
if (!lastNew) return [];
|
|
324
|
-
const maxK = Math.min(lastNew.index + 1, startLine - 1);
|
|
325
|
-
let runLen = 0;
|
|
326
|
-
while (
|
|
327
|
-
runLen < maxK &&
|
|
328
|
-
canonLines[startLine - 2 - runLen]!.length > 0 &&
|
|
329
|
-
canon(contentLines[lastNew.index - runLen]!) === canonLines[startLine - 2 - runLen]!
|
|
330
|
-
) {
|
|
331
|
-
runLen++;
|
|
332
|
-
}
|
|
333
|
-
if (runLen === 0) return [];
|
|
334
|
-
const sectionStart = startLine - 1 - runLen;
|
|
335
|
-
if (!sectionIsUnique(canonLines, sectionStart, runLen)) return [];
|
|
336
|
-
const dups: BDup[] = [];
|
|
337
|
-
for (let k = 0; k < runLen; k++) {
|
|
338
|
-
dups.push({ kind: "last-new-before", replacementLineIndex: lastNew.index - k });
|
|
339
|
-
}
|
|
340
|
-
return dups;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function canonCounts(lines: string[]): Map<string, number> {
|
|
344
|
-
const counts = new Map<string, number>();
|
|
345
|
-
for (const line of lines) {
|
|
346
|
-
const key = canon(line);
|
|
347
|
-
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
348
|
-
}
|
|
349
|
-
return counts;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
export function findNewEdge(
|
|
353
|
-
contentLines: string[],
|
|
354
|
-
rangeLines: string[],
|
|
355
|
-
fromEnd: boolean,
|
|
356
|
-
): { index: number; line: string } | undefined {
|
|
357
|
-
const multiset = canonCounts(rangeLines);
|
|
358
|
-
const step = fromEnd ? -1 : 1;
|
|
359
|
-
const start = fromEnd ? contentLines.length - 1 : 0;
|
|
360
|
-
for (let i = start; i >= 0 && i < contentLines.length; i += step) {
|
|
361
|
-
const line = contentLines[i]!;
|
|
362
|
-
const key = canon(line);
|
|
363
|
-
if (key.length === 0) continue;
|
|
364
|
-
const count = multiset.get(key) ?? 0;
|
|
365
|
-
if (count > 0) {
|
|
366
|
-
multiset.set(key, count - 1);
|
|
367
|
-
} else {
|
|
368
|
-
return { index: i, line };
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
return undefined;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
232
|
export function valEdit(
|
|
375
233
|
edit: HEdit,
|
|
376
234
|
fileLines: string[],
|
|
377
235
|
fileHashes: string[],
|
|
378
236
|
warnings: string[],
|
|
379
237
|
signal: AbortSignal | undefined,
|
|
380
|
-
): { resolved: RHEdit | undefined; mismatches: HMismatch[]
|
|
238
|
+
): { resolved: RHEdit | undefined; mismatches: HMismatch[] } {
|
|
381
239
|
assertAligned(fileLines, fileHashes, "valEdit");
|
|
382
240
|
const mismatches: HMismatch[] = [];
|
|
383
|
-
const boundaryDups: BDup[] = [];
|
|
384
241
|
|
|
385
242
|
const hashIndex = new Map<string, number[]>();
|
|
386
243
|
for (let i = 0; i < fileHashes.length; i++) {
|
|
@@ -410,26 +267,14 @@ export function valEdit(
|
|
|
410
267
|
const endMismatch = mismatches.findLast((m) => m.ref === edit.hash_bounds[1]);
|
|
411
268
|
if (endMismatch && endMismatch.kind === "not_found") endMismatch.context = startResolved;
|
|
412
269
|
}
|
|
413
|
-
return { resolved: undefined, mismatches
|
|
270
|
+
return { resolved: undefined, mismatches };
|
|
414
271
|
}
|
|
415
|
-
const endLine = endResolved.line;
|
|
416
|
-
const rangeLines = fileLines.slice(startResolved.line - 1, endLine);
|
|
417
|
-
const canonCache = new Map<string, string>();
|
|
418
|
-
const canonLines = fileLines.map((line) => getCached(canonCache, line, canon));
|
|
419
|
-
boundaryDups.push(
|
|
420
|
-
...trailingDups(edit.content_lines, fileLines, endLine),
|
|
421
|
-
...leadingDups(edit.content_lines, fileLines, startResolved.line),
|
|
422
|
-
...firstNewAfterDups(edit.content_lines, rangeLines, canonLines, endLine),
|
|
423
|
-
...lastNewBeforeDups(edit.content_lines, rangeLines, canonLines, startResolved.line),
|
|
424
|
-
);
|
|
425
|
-
|
|
426
272
|
return {
|
|
427
273
|
resolved: {
|
|
428
274
|
content_lines: edit.content_lines,
|
|
429
275
|
hash_bounds: [startResolved, endResolved],
|
|
430
276
|
},
|
|
431
277
|
mismatches,
|
|
432
|
-
boundaryDups,
|
|
433
278
|
};
|
|
434
279
|
}
|
|
435
280
|
|
package/src/insert.ts
CHANGED
|
@@ -119,7 +119,6 @@ export async function insertPreview(request: unknown, cwd: string, signal?: Abor
|
|
|
119
119
|
accessMode: constants.R_OK,
|
|
120
120
|
noPersist: true,
|
|
121
121
|
preloadedNorm: preload,
|
|
122
|
-
skipBoundaryDedup: true,
|
|
123
122
|
signal,
|
|
124
123
|
});
|
|
125
124
|
return previewFromPipe(pipe);
|
|
@@ -224,8 +223,6 @@ export function buildInsertToolDef(flags: EditToolFlags = DEFAULT_EDIT_FLAGS): I
|
|
|
224
223
|
signal,
|
|
225
224
|
hedit,
|
|
226
225
|
extraWarnings: [...anchorWarnings, ...insertWarnings, ...resWarnings],
|
|
227
|
-
skipBoundaryDedup: true,
|
|
228
|
-
strictBoundaryDedup: false,
|
|
229
226
|
foldedLines: built.anchorLine === undefined ? 0 : 1,
|
|
230
227
|
});
|
|
231
228
|
}
|
|
@@ -239,7 +236,6 @@ export function buildInsertToolDef(flags: EditToolFlags = DEFAULT_EDIT_FLAGS): I
|
|
|
239
236
|
accessMode: constants.R_OK | constants.W_OK,
|
|
240
237
|
signal,
|
|
241
238
|
preloadedNorm: preload,
|
|
242
|
-
skipBoundaryDedup: true,
|
|
243
239
|
});
|
|
244
240
|
return commitEdit(pipe, {
|
|
245
241
|
path: pipe.path,
|
package/src/replace-diff.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
HASH_CLASS,
|
|
8
8
|
changedRange,
|
|
9
9
|
} from "./hashline";
|
|
10
|
-
import { MAX_DIFF_INPUT_BYTES
|
|
10
|
+
import { MAX_DIFF_INPUT_BYTES } from "./constants";
|
|
11
11
|
import { splitLines } from "./utils";
|
|
12
12
|
import {
|
|
13
13
|
detectEnding,
|
|
@@ -23,8 +23,6 @@ export interface DiffSpan {
|
|
|
23
23
|
start: number;
|
|
24
24
|
end: number;
|
|
25
25
|
replacementCount: number;
|
|
26
|
-
dedupAbove?: string[];
|
|
27
|
-
dedupBelow?: string[];
|
|
28
26
|
}
|
|
29
27
|
|
|
30
28
|
interface PlacedSpan {
|
|
@@ -32,16 +30,6 @@ interface PlacedSpan {
|
|
|
32
30
|
oldEnd: number;
|
|
33
31
|
newStart: number;
|
|
34
32
|
newEnd: number;
|
|
35
|
-
dedupAbove?: string[];
|
|
36
|
-
dedupBelow?: string[];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function fmtDedupRow(line: string): string {
|
|
40
|
-
const row = `${DEDUP_ANCHOR}${HASH_SEP}${line}`;
|
|
41
|
-
if (Buffer.byteLength(row, "utf-8") <= DEFAULT_MAX_BYTES) return row;
|
|
42
|
-
const size = formatSize(Buffer.byteLength(row, "utf-8"));
|
|
43
|
-
const limit = formatSize(DEFAULT_MAX_BYTES);
|
|
44
|
-
return `${DEDUP_ANCHOR}${HASH_SEP}[Row is ${size}, exceeds ${limit}; content not shown. Use read to see the full line.]`;
|
|
45
33
|
}
|
|
46
34
|
|
|
47
35
|
function fmtDiffLine(
|
|
@@ -168,7 +156,7 @@ function placeSpans(
|
|
|
168
156
|
if (span.replacementCount === 0) {
|
|
169
157
|
if (newEnd !== newStart - 1) return undefined;
|
|
170
158
|
} else if (newEnd >= newLen) return undefined;
|
|
171
|
-
placed.push({ oldStart: span.start, oldEnd: span.end, newStart, newEnd
|
|
159
|
+
placed.push({ oldStart: span.start, oldEnd: span.end, newStart, newEnd });
|
|
172
160
|
offset += span.replacementCount - spanLen;
|
|
173
161
|
removedSum += spanLen;
|
|
174
162
|
addedSum += span.replacementCount;
|
|
@@ -197,7 +185,7 @@ function trimPlacedSpans(
|
|
|
197
185
|
newEnd -= 1;
|
|
198
186
|
}
|
|
199
187
|
if (oldStart > oldEnd && newStart > newEnd) continue;
|
|
200
|
-
out.push({ oldStart, oldEnd, newStart, newEnd
|
|
188
|
+
out.push({ oldStart, oldEnd, newStart, newEnd });
|
|
201
189
|
}
|
|
202
190
|
return out;
|
|
203
191
|
}
|
|
@@ -212,7 +200,6 @@ function createRowEmitter(
|
|
|
212
200
|
readonly truncated: boolean;
|
|
213
201
|
emitPlain(line: string, num?: number): void;
|
|
214
202
|
emitRow(prefix: " " | "+" | "-", line: string, hash: string | undefined, num?: number): void;
|
|
215
|
-
emitDedup(line: string): boolean;
|
|
216
203
|
} {
|
|
217
204
|
let outBytes = 0;
|
|
218
205
|
let stopped = false;
|
|
@@ -247,26 +234,11 @@ function createRowEmitter(
|
|
|
247
234
|
output.push(full);
|
|
248
235
|
lineNumbers.push(num);
|
|
249
236
|
};
|
|
250
|
-
const emitDedup = (line: string): boolean => {
|
|
251
|
-
if (stopped) return false;
|
|
252
|
-
const row = fmtDedupRow(line);
|
|
253
|
-
const rowBytes = Buffer.byteLength(row, "utf-8") + 1;
|
|
254
|
-
if (outBytes + rowBytes > maxBytes) {
|
|
255
|
-
stopped = true;
|
|
256
|
-
truncated = true;
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
outBytes += rowBytes;
|
|
260
|
-
output.push(row);
|
|
261
|
-
lineNumbers.push(undefined);
|
|
262
|
-
return true;
|
|
263
|
-
};
|
|
264
237
|
return {
|
|
265
238
|
get stopped() { return stopped; },
|
|
266
239
|
get truncated() { return truncated; },
|
|
267
240
|
emitPlain,
|
|
268
241
|
emitRow,
|
|
269
|
-
emitDedup,
|
|
270
242
|
};
|
|
271
243
|
}
|
|
272
244
|
|
|
@@ -279,7 +251,7 @@ function genSpanDiff(
|
|
|
279
251
|
maxLineBytes: number,
|
|
280
252
|
maxBytes: number,
|
|
281
253
|
spans: DiffSpan[],
|
|
282
|
-
): { diff: string; firstChangedLine: number | undefined; lineNumbers: (number | undefined)[]
|
|
254
|
+
): { diff: string; firstChangedLine: number | undefined; lineNumbers: (number | undefined)[] } | undefined {
|
|
283
255
|
const oldLines = splitLines(oldContent);
|
|
284
256
|
const newLines = splitLines(newContent);
|
|
285
257
|
if (oldHashes.length !== oldLines.length || newHashes.length !== newLines.length) return undefined;
|
|
@@ -301,7 +273,6 @@ function genSpanDiff(
|
|
|
301
273
|
return undefined;
|
|
302
274
|
}
|
|
303
275
|
const output: string[] = [];
|
|
304
|
-
let dedupEmitted = false;
|
|
305
276
|
const lineNumbers: (number | undefined)[] = [];
|
|
306
277
|
let firstChangedLine: number | undefined;
|
|
307
278
|
const em = createRowEmitter(output, lineNumbers, maxLineBytes, maxBytes);
|
|
@@ -360,20 +331,12 @@ function genSpanDiff(
|
|
|
360
331
|
emitGap(newPos, gapLen, spanIdx === 0 ? "leading" : "middle");
|
|
361
332
|
if (em.stopped) break;
|
|
362
333
|
if (firstChangedLine === undefined) firstChangedLine = span.newStart + 1;
|
|
363
|
-
for (const dedup of span.dedupAbove ?? []) {
|
|
364
|
-
if (em.stopped) break;
|
|
365
|
-
if (em.emitDedup(dedup)) dedupEmitted = true;
|
|
366
|
-
}
|
|
367
334
|
for (let oldIdx = span.oldStart; oldIdx <= span.oldEnd && !em.stopped; oldIdx++) {
|
|
368
335
|
em.emitRow("-", oldLines[oldIdx]!, oldHashes[oldIdx], oldIdx + 1);
|
|
369
336
|
}
|
|
370
337
|
for (let newIdx = span.newStart; newIdx <= span.newEnd && !em.stopped; newIdx++) {
|
|
371
338
|
em.emitRow("+", newLines[newIdx]!, newHashes[newIdx], newIdx + 1);
|
|
372
339
|
}
|
|
373
|
-
for (const dedup of span.dedupBelow ?? []) {
|
|
374
|
-
if (em.stopped) break;
|
|
375
|
-
if (em.emitDedup(dedup)) dedupEmitted = true;
|
|
376
|
-
}
|
|
377
340
|
oldPos = span.oldEnd + 1;
|
|
378
341
|
newPos = span.newEnd + 1;
|
|
379
342
|
}
|
|
@@ -387,7 +350,7 @@ function genSpanDiff(
|
|
|
387
350
|
output.push(`[diff truncated at ${formatSize(maxBytes)}; use read to see the rest.]`);
|
|
388
351
|
lineNumbers.push(undefined);
|
|
389
352
|
}
|
|
390
|
-
return { diff: output.join("\n"), firstChangedLine, lineNumbers
|
|
353
|
+
return { diff: output.join("\n"), firstChangedLine, lineNumbers };
|
|
391
354
|
}
|
|
392
355
|
|
|
393
356
|
function overDiffInputLimit(oldContent: string, newContent: string): boolean {
|
|
@@ -402,14 +365,14 @@ export function genDiff(
|
|
|
402
365
|
oldContentHashes?: string[],
|
|
403
366
|
limits?: DiffLimits,
|
|
404
367
|
spans?: DiffSpan[],
|
|
405
|
-
): { diff: string; firstChangedLine: number | undefined; lineNumbers: (number|undefined)[]
|
|
368
|
+
): { diff: string; firstChangedLine: number | undefined; lineNumbers: (number|undefined)[] } {
|
|
406
369
|
const maxLineBytes = limits?.unlimited ? Number.POSITIVE_INFINITY : (limits?.maxLineBytes ?? DEFAULT_MAX_BYTES);
|
|
407
370
|
const maxBytes = limits?.unlimited ? Number.POSITIVE_INFINITY : (limits?.maxBytes ?? DEFAULT_MAX_BYTES);
|
|
408
371
|
if (spans && newContentHashes && oldContentHashes) {
|
|
409
372
|
const anchored = genSpanDiff(oldContent, newContent, contextLines, newContentHashes, oldContentHashes, maxLineBytes, maxBytes, spans);
|
|
410
373
|
if (anchored) {
|
|
411
374
|
const diff = disambiguateDuplicateAnchors(anchored.diff);
|
|
412
|
-
return { diff, firstChangedLine: anchored.firstChangedLine, lineNumbers: anchored.lineNumbers
|
|
375
|
+
return { diff, firstChangedLine: anchored.firstChangedLine, lineNumbers: anchored.lineNumbers };
|
|
413
376
|
}
|
|
414
377
|
}
|
|
415
378
|
if (!limits?.unlimited && overDiffInputLimit(oldContent, newContent)) {
|
package/src/replace-render.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { keyHint, type Theme } from "@earendil-works/pi-coding-agent";
|
|
|
3
3
|
import type { ReplaceDetails } from "./replace";
|
|
4
4
|
import { abortedBatchMessageFor } from "./batch";
|
|
5
5
|
import { withLineNumbers } from "./utils";
|
|
6
|
-
import { isDedupRow } from "./replace-response";
|
|
7
6
|
import { getPreviewInput } from "./payload-contract";
|
|
8
7
|
export { getPreviewInput };
|
|
9
8
|
|
|
@@ -28,7 +27,6 @@ type DiffRowKind = "added" | "removed" | "context";
|
|
|
28
27
|
|
|
29
28
|
function diffRowKind(line: string): DiffRowKind {
|
|
30
29
|
const withoutGutter = line.replace(/^\s*\d+\s+│\s*/, "");
|
|
31
|
-
if (isDedupRow(withoutGutter) || isDedupRow(withoutGutter.replace(/^\s*│\s*/, ""))) return "removed";
|
|
32
30
|
if (withoutGutter.startsWith("+") && !withoutGutter.startsWith("+++")) return "added";
|
|
33
31
|
if (withoutGutter.startsWith("-") && !withoutGutter.startsWith("---")) return "removed";
|
|
34
32
|
return "context";
|