pi-openai-codex-compat 0.0.6 → 0.0.7
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/APPLY_PATCH_INSTRUCTION_FEEDBACK.md +617 -0
- package/CHANGELOG.md +44 -0
- package/LICENSES/tree-sitter-wasms-MIT.txt +21 -0
- package/LICENSES/web-tree-sitter-MIT.txt +21 -0
- package/README.md +37 -9
- package/THIRD_PARTY_NOTICES.md +26 -0
- package/extensions/openai-codex-compat/apply-patch-diff-render.ts +499 -20
- package/extensions/openai-codex-compat/apply-patch-engine.ts +4085 -491
- package/extensions/openai-codex-compat/apply-patch-matcher.ts +1535 -0
- package/extensions/openai-codex-compat/apply-patch-render.ts +85 -19
- package/extensions/openai-codex-compat/apply-patch.ts +41 -5
- package/extensions/openai-codex-compat/codex-provider.ts +252 -14
- package/extensions/openai-codex-compat/codex-stream.ts +78 -61
- package/extensions/openai-codex-compat/compaction-checkpoint.ts +28 -0
- package/extensions/openai-codex-compat/config.ts +18 -0
- package/extensions/openai-codex-compat/footer.ts +4 -8
- package/extensions/openai-codex-compat/index.ts +4 -1
- package/extensions/openai-codex-compat/remote-compaction.ts +4 -0
- package/extensions/openai-codex-compat/settings-pane.ts +11 -0
- package/extensions/openai-codex-compat/tools.ts +2 -1
- package/package.json +10 -4
|
@@ -10,8 +10,24 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
type AppliedPatchChange,
|
|
12
12
|
type ApplyPatchDetails,
|
|
13
|
+
type ApplyPatchFinalPathState,
|
|
14
|
+
type ApplyPatchFailureDetails,
|
|
15
|
+
type ApplyPatchInstructionDetails,
|
|
16
|
+
type ApplyPatchInstructionEffect,
|
|
17
|
+
type ApplyPatchInstructionReason,
|
|
18
|
+
type ApplyPatchInstructionStatus,
|
|
19
|
+
applyPatchHasOtherFilesystemChanges,
|
|
20
|
+
applyPatchNeedsInstructionResults,
|
|
21
|
+
applyPatchSummaryPaths,
|
|
13
22
|
coalesceAppliedPatchChangesForRendering,
|
|
23
|
+
formatApplyPatchFailureHeading,
|
|
24
|
+
formatApplyPatchInstructionFeedback,
|
|
25
|
+
formatApplyPatchInstructionStatusLabel,
|
|
14
26
|
} from "./apply-patch-engine.ts";
|
|
27
|
+
import type {
|
|
28
|
+
FormatterMatchCandidateRange,
|
|
29
|
+
FormatterMatchFailureDetails,
|
|
30
|
+
} from "./apply-patch-matcher.ts";
|
|
15
31
|
import { usesLightToolPalette } from "./codex-tool-surface.ts";
|
|
16
32
|
|
|
17
33
|
type DiffLineKind = "add" | "delete" | "context";
|
|
@@ -73,6 +89,10 @@ function displayPath(path: string, cwd: string): string {
|
|
|
73
89
|
}
|
|
74
90
|
|
|
75
91
|
function changePath(change: AppliedPatchChange, cwd: string): string {
|
|
92
|
+
if (change.kind === "move") {
|
|
93
|
+
const path = `${displayPath(change.sourcePath, cwd)} → ${displayPath(change.destinationPath, cwd)}`;
|
|
94
|
+
return change.replacedDestination ? `${path} (replaced destination)` : path;
|
|
95
|
+
}
|
|
76
96
|
const path = displayPath(change.path, cwd);
|
|
77
97
|
return change.kind === "update" && change.moveTo
|
|
78
98
|
? `${path} → ${displayPath(change.moveTo, cwd)}`
|
|
@@ -84,12 +104,21 @@ function changeVerb(change: AppliedPatchChange): string {
|
|
|
84
104
|
case "add":
|
|
85
105
|
return "Added";
|
|
86
106
|
case "delete":
|
|
87
|
-
return "Deleted";
|
|
107
|
+
return change.entryType === "symlink" ? "Deleted symlink" : "Deleted";
|
|
108
|
+
case "move":
|
|
109
|
+
return "Moved";
|
|
88
110
|
case "update":
|
|
89
111
|
return "Edited";
|
|
90
112
|
}
|
|
91
113
|
}
|
|
92
114
|
|
|
115
|
+
function changeListPath(change: AppliedPatchChange, cwd: string): string {
|
|
116
|
+
const path = changePath(change, cwd);
|
|
117
|
+
return change.kind === "delete" && change.entryType === "symlink"
|
|
118
|
+
? `${path} (deleted symlink)`
|
|
119
|
+
: path;
|
|
120
|
+
}
|
|
121
|
+
|
|
93
122
|
function countSummary(additions: number, deletions: number, theme: Theme): string {
|
|
94
123
|
return `(${theme.fg("success", `+${additions}`)} ${theme.fg("error", `-${deletions}`)})`;
|
|
95
124
|
}
|
|
@@ -109,6 +138,11 @@ function isAppliedPatchChange(value: unknown): value is AppliedPatchChange {
|
|
|
109
138
|
kind?: unknown;
|
|
110
139
|
path?: unknown;
|
|
111
140
|
moveTo?: unknown;
|
|
141
|
+
sourcePath?: unknown;
|
|
142
|
+
destinationPath?: unknown;
|
|
143
|
+
replacedDestination?: unknown;
|
|
144
|
+
entryType?: unknown;
|
|
145
|
+
exact?: unknown;
|
|
112
146
|
content?: unknown;
|
|
113
147
|
oldContent?: unknown;
|
|
114
148
|
newContent?: unknown;
|
|
@@ -117,15 +151,27 @@ function isAppliedPatchChange(value: unknown): value is AppliedPatchChange {
|
|
|
117
151
|
deletions?: unknown;
|
|
118
152
|
};
|
|
119
153
|
if (
|
|
120
|
-
typeof change.path !== "string" ||
|
|
121
154
|
typeof change.displayDiff !== "string" ||
|
|
122
155
|
typeof change.additions !== "number" ||
|
|
123
156
|
typeof change.deletions !== "number"
|
|
124
157
|
) {
|
|
125
158
|
return false;
|
|
126
159
|
}
|
|
160
|
+
if (change.kind === "move") {
|
|
161
|
+
return (
|
|
162
|
+
typeof change.sourcePath === "string" &&
|
|
163
|
+
typeof change.destinationPath === "string" &&
|
|
164
|
+
typeof change.replacedDestination === "boolean" &&
|
|
165
|
+
(change.entryType === "regular-file" || change.entryType === "symlink") &&
|
|
166
|
+
typeof change.exact === "boolean"
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if (typeof change.path !== "string") return false;
|
|
127
170
|
if (change.kind === "add" || change.kind === "delete") {
|
|
128
|
-
return
|
|
171
|
+
return change.kind === "delete"
|
|
172
|
+
? (change.entryType === "regular-file" || change.entryType === "symlink") &&
|
|
173
|
+
(change.content === undefined || typeof change.content === "string")
|
|
174
|
+
: typeof change.content === "string";
|
|
129
175
|
}
|
|
130
176
|
return (
|
|
131
177
|
change.kind === "update" &&
|
|
@@ -135,6 +181,244 @@ function isAppliedPatchChange(value: unknown): value is AppliedPatchChange {
|
|
|
135
181
|
);
|
|
136
182
|
}
|
|
137
183
|
|
|
184
|
+
const INSTRUCTION_STATUSES = new Set<ApplyPatchInstructionStatus>([
|
|
185
|
+
"applied",
|
|
186
|
+
"planned",
|
|
187
|
+
"no-op",
|
|
188
|
+
"dead",
|
|
189
|
+
"failed",
|
|
190
|
+
"not-run",
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
const INSTRUCTION_REASON_CODES = new Set<ApplyPatchInstructionReason["code"]>([
|
|
194
|
+
"empty-update",
|
|
195
|
+
"identity-update",
|
|
196
|
+
"content-already-present",
|
|
197
|
+
"update-result-unchanged",
|
|
198
|
+
"path-already-absent",
|
|
199
|
+
"same-entry-move",
|
|
200
|
+
"move-already-fulfilled",
|
|
201
|
+
"dead-dominated",
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
function isApplyPatchInstructionReason(value: unknown): value is ApplyPatchInstructionReason {
|
|
205
|
+
if (typeof value !== "object" || value === null) return false;
|
|
206
|
+
const reason = value as {
|
|
207
|
+
code?: unknown;
|
|
208
|
+
message?: unknown;
|
|
209
|
+
dominatingInstructions?: unknown;
|
|
210
|
+
relatedInstructions?: unknown;
|
|
211
|
+
};
|
|
212
|
+
return (
|
|
213
|
+
typeof reason.code === "string" &&
|
|
214
|
+
INSTRUCTION_REASON_CODES.has(reason.code as ApplyPatchInstructionReason["code"]) &&
|
|
215
|
+
typeof reason.message === "string" &&
|
|
216
|
+
(reason.dominatingInstructions === undefined ||
|
|
217
|
+
(Array.isArray(reason.dominatingInstructions) &&
|
|
218
|
+
reason.dominatingInstructions.every((index) => typeof index === "number"))) &&
|
|
219
|
+
(reason.relatedInstructions === undefined ||
|
|
220
|
+
(Array.isArray(reason.relatedInstructions) &&
|
|
221
|
+
reason.relatedInstructions.every((index) => typeof index === "number"))) &&
|
|
222
|
+
(reason.code !== "move-already-fulfilled" ||
|
|
223
|
+
(Array.isArray(reason.relatedInstructions) && reason.relatedInstructions.length === 1))
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const INSTRUCTION_EFFECT_KINDS = new Set<ApplyPatchInstructionEffect["kind"]>([
|
|
228
|
+
"created",
|
|
229
|
+
"replaced",
|
|
230
|
+
"updated",
|
|
231
|
+
"deleted",
|
|
232
|
+
"directory-created",
|
|
233
|
+
"temporary-entry-remains",
|
|
234
|
+
"source-remains",
|
|
235
|
+
"symlink-removed",
|
|
236
|
+
"symlink-moved",
|
|
237
|
+
"symlink-target-modified",
|
|
238
|
+
]);
|
|
239
|
+
|
|
240
|
+
function isApplyPatchInstructionEffect(value: unknown): value is ApplyPatchInstructionEffect {
|
|
241
|
+
if (typeof value !== "object" || value === null) return false;
|
|
242
|
+
const effect = value as {
|
|
243
|
+
kind?: unknown;
|
|
244
|
+
path?: unknown;
|
|
245
|
+
previousEntry?: unknown;
|
|
246
|
+
replacementEntry?: unknown;
|
|
247
|
+
target?: unknown;
|
|
248
|
+
};
|
|
249
|
+
if (
|
|
250
|
+
typeof effect.kind !== "string" ||
|
|
251
|
+
!INSTRUCTION_EFFECT_KINDS.has(effect.kind as ApplyPatchInstructionEffect["kind"]) ||
|
|
252
|
+
typeof effect.path !== "string"
|
|
253
|
+
) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
if (effect.kind === "replaced") {
|
|
257
|
+
const isFileEntry = (entry: unknown): boolean => {
|
|
258
|
+
if (typeof entry !== "object" || entry === null) return false;
|
|
259
|
+
const candidate = entry as { entryType?: unknown; target?: unknown };
|
|
260
|
+
return (
|
|
261
|
+
candidate.entryType === "regular-file" ||
|
|
262
|
+
(candidate.entryType === "symlink" && typeof candidate.target === "string")
|
|
263
|
+
);
|
|
264
|
+
};
|
|
265
|
+
return isFileEntry(effect.previousEntry) && isFileEntry(effect.replacementEntry);
|
|
266
|
+
}
|
|
267
|
+
if (
|
|
268
|
+
effect.kind === "symlink-removed" ||
|
|
269
|
+
effect.kind === "symlink-moved" ||
|
|
270
|
+
effect.kind === "symlink-target-modified"
|
|
271
|
+
) {
|
|
272
|
+
return typeof effect.target === "string";
|
|
273
|
+
}
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const FINAL_PATH_STATES = new Set<ApplyPatchFinalPathState["state"]>([
|
|
278
|
+
"absent",
|
|
279
|
+
"regular-file",
|
|
280
|
+
"symlink",
|
|
281
|
+
"directory",
|
|
282
|
+
"other-entry",
|
|
283
|
+
"unchanged",
|
|
284
|
+
"requested-content",
|
|
285
|
+
"different-from-requested-content",
|
|
286
|
+
"different-from-requested-and-previous-content",
|
|
287
|
+
"different-from-previous-content",
|
|
288
|
+
"different-entry",
|
|
289
|
+
"different-entry-type",
|
|
290
|
+
"not-verified",
|
|
291
|
+
]);
|
|
292
|
+
|
|
293
|
+
function isApplyPatchFinalPathState(value: unknown): value is ApplyPatchFinalPathState {
|
|
294
|
+
if (typeof value !== "object" || value === null) return false;
|
|
295
|
+
const state = value as { path?: unknown; state?: unknown };
|
|
296
|
+
return (
|
|
297
|
+
typeof state.path === "string" &&
|
|
298
|
+
typeof state.state === "string" &&
|
|
299
|
+
FINAL_PATH_STATES.has(state.state as ApplyPatchFinalPathState["state"])
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function isApplyPatchInstruction(value: unknown): value is ApplyPatchInstructionDetails {
|
|
304
|
+
if (typeof value !== "object" || value === null) return false;
|
|
305
|
+
const instruction = value as {
|
|
306
|
+
index?: unknown;
|
|
307
|
+
kind?: unknown;
|
|
308
|
+
path?: unknown;
|
|
309
|
+
moveTo?: unknown;
|
|
310
|
+
status?: unknown;
|
|
311
|
+
reason?: unknown;
|
|
312
|
+
effects?: unknown;
|
|
313
|
+
finalStates?: unknown;
|
|
314
|
+
matcher?: unknown;
|
|
315
|
+
changeIndexes?: unknown;
|
|
316
|
+
error?: unknown;
|
|
317
|
+
};
|
|
318
|
+
return (
|
|
319
|
+
typeof instruction.index === "number" &&
|
|
320
|
+
(instruction.kind === "add" ||
|
|
321
|
+
instruction.kind === "delete" ||
|
|
322
|
+
instruction.kind === "update" ||
|
|
323
|
+
instruction.kind === "move") &&
|
|
324
|
+
typeof instruction.path === "string" &&
|
|
325
|
+
(instruction.moveTo === undefined || typeof instruction.moveTo === "string") &&
|
|
326
|
+
typeof instruction.status === "string" &&
|
|
327
|
+
INSTRUCTION_STATUSES.has(instruction.status as ApplyPatchInstructionStatus) &&
|
|
328
|
+
(instruction.reason === undefined || isApplyPatchInstructionReason(instruction.reason)) &&
|
|
329
|
+
(instruction.effects === undefined ||
|
|
330
|
+
(Array.isArray(instruction.effects) &&
|
|
331
|
+
instruction.effects.every(isApplyPatchInstructionEffect))) &&
|
|
332
|
+
(instruction.finalStates === undefined ||
|
|
333
|
+
(Array.isArray(instruction.finalStates) &&
|
|
334
|
+
instruction.finalStates.every(isApplyPatchFinalPathState))) &&
|
|
335
|
+
(instruction.matcher === undefined || isFormatterMatchFailure(instruction.matcher)) &&
|
|
336
|
+
(instruction.changeIndexes === undefined ||
|
|
337
|
+
(Array.isArray(instruction.changeIndexes) &&
|
|
338
|
+
instruction.changeIndexes.every((index) => typeof index === "number"))) &&
|
|
339
|
+
(instruction.error === undefined || typeof instruction.error === "string")
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function isMatcherRange(value: unknown): value is FormatterMatchCandidateRange {
|
|
344
|
+
if (typeof value !== "object" || value === null) return false;
|
|
345
|
+
const range = value as { startLine?: unknown; endLine?: unknown };
|
|
346
|
+
return typeof range.startLine === "number" && typeof range.endLine === "number";
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const MATCHER_REASONS = new Set<FormatterMatchFailureDetails["reason"]>([
|
|
350
|
+
"no-candidate",
|
|
351
|
+
"no-ordered-mapping",
|
|
352
|
+
"too-many-candidates",
|
|
353
|
+
"ambiguous-output",
|
|
354
|
+
"mapping-limit",
|
|
355
|
+
"overlapping-edits",
|
|
356
|
+
]);
|
|
357
|
+
|
|
358
|
+
function isFormatterMatchFailure(value: unknown): value is FormatterMatchFailureDetails {
|
|
359
|
+
if (typeof value !== "object" || value === null) return false;
|
|
360
|
+
const failure = value as {
|
|
361
|
+
reason?: unknown;
|
|
362
|
+
path?: unknown;
|
|
363
|
+
groupCount?: unknown;
|
|
364
|
+
groupIndex?: unknown;
|
|
365
|
+
chunkCount?: unknown;
|
|
366
|
+
chunkIndex?: unknown;
|
|
367
|
+
candidateCount?: unknown;
|
|
368
|
+
candidates?: unknown;
|
|
369
|
+
previousGroupIndex?: unknown;
|
|
370
|
+
previousCandidates?: unknown;
|
|
371
|
+
reverseOrdered?: unknown;
|
|
372
|
+
overlapping?: unknown;
|
|
373
|
+
replacementCandidateCount?: unknown;
|
|
374
|
+
replacementCandidates?: unknown;
|
|
375
|
+
oldExcerpt?: unknown;
|
|
376
|
+
};
|
|
377
|
+
return (
|
|
378
|
+
typeof failure.reason === "string" &&
|
|
379
|
+
MATCHER_REASONS.has(failure.reason as FormatterMatchFailureDetails["reason"]) &&
|
|
380
|
+
typeof failure.path === "string" &&
|
|
381
|
+
typeof failure.groupCount === "number" &&
|
|
382
|
+
(failure.groupIndex === undefined || typeof failure.groupIndex === "number") &&
|
|
383
|
+
(failure.chunkCount === undefined || typeof failure.chunkCount === "number") &&
|
|
384
|
+
(failure.chunkIndex === undefined || typeof failure.chunkIndex === "number") &&
|
|
385
|
+
typeof failure.candidateCount === "number" &&
|
|
386
|
+
Array.isArray(failure.candidates) &&
|
|
387
|
+
failure.candidates.every(isMatcherRange) &&
|
|
388
|
+
(failure.previousGroupIndex === undefined || typeof failure.previousGroupIndex === "number") &&
|
|
389
|
+
(failure.previousCandidates === undefined ||
|
|
390
|
+
(Array.isArray(failure.previousCandidates) &&
|
|
391
|
+
failure.previousCandidates.every(isMatcherRange))) &&
|
|
392
|
+
(failure.reverseOrdered === undefined || typeof failure.reverseOrdered === "boolean") &&
|
|
393
|
+
(failure.overlapping === undefined || typeof failure.overlapping === "boolean") &&
|
|
394
|
+
(failure.replacementCandidateCount === undefined ||
|
|
395
|
+
typeof failure.replacementCandidateCount === "number") &&
|
|
396
|
+
(failure.replacementCandidates === undefined ||
|
|
397
|
+
(Array.isArray(failure.replacementCandidates) &&
|
|
398
|
+
failure.replacementCandidates.every(isMatcherRange))) &&
|
|
399
|
+
(failure.oldExcerpt === undefined || typeof failure.oldExcerpt === "string")
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function isApplyPatchFailure(value: unknown): value is ApplyPatchFailureDetails {
|
|
404
|
+
if (typeof value !== "object" || value === null) return false;
|
|
405
|
+
const failure = value as {
|
|
406
|
+
phase?: unknown;
|
|
407
|
+
message?: unknown;
|
|
408
|
+
failedInstruction?: unknown;
|
|
409
|
+
matcher?: unknown;
|
|
410
|
+
};
|
|
411
|
+
return (
|
|
412
|
+
(failure.phase === "input" ||
|
|
413
|
+
failure.phase === "parse" ||
|
|
414
|
+
failure.phase === "preflight" ||
|
|
415
|
+
failure.phase === "execution") &&
|
|
416
|
+
typeof failure.message === "string" &&
|
|
417
|
+
(failure.failedInstruction === undefined || typeof failure.failedInstruction === "number") &&
|
|
418
|
+
(failure.matcher === undefined || isFormatterMatchFailure(failure.matcher))
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
138
422
|
export function isApplyPatchDetails(value: unknown): value is ApplyPatchDetails {
|
|
139
423
|
if (typeof value !== "object" || value === null) return false;
|
|
140
424
|
const details = value as {
|
|
@@ -144,6 +428,9 @@ export function isApplyPatchDetails(value: unknown): value is ApplyPatchDetails
|
|
|
144
428
|
added?: unknown;
|
|
145
429
|
modified?: unknown;
|
|
146
430
|
deleted?: unknown;
|
|
431
|
+
instructions?: unknown;
|
|
432
|
+
failure?: unknown;
|
|
433
|
+
error?: unknown;
|
|
147
434
|
};
|
|
148
435
|
return (
|
|
149
436
|
(details.status === "completed" || details.status === "failed") &&
|
|
@@ -152,14 +439,22 @@ export function isApplyPatchDetails(value: unknown): value is ApplyPatchDetails
|
|
|
152
439
|
details.changes.every(isAppliedPatchChange) &&
|
|
153
440
|
isStringArray(details.added) &&
|
|
154
441
|
isStringArray(details.modified) &&
|
|
155
|
-
isStringArray(details.deleted)
|
|
442
|
+
isStringArray(details.deleted) &&
|
|
443
|
+
(details.instructions === undefined ||
|
|
444
|
+
(Array.isArray(details.instructions) &&
|
|
445
|
+
details.instructions.every(isApplyPatchInstruction))) &&
|
|
446
|
+
(details.failure === undefined || isApplyPatchFailure(details.failure)) &&
|
|
447
|
+
(details.error === undefined || typeof details.error === "string")
|
|
156
448
|
);
|
|
157
449
|
}
|
|
158
450
|
|
|
159
451
|
function sortedChanges(details: ApplyPatchDetails, cwd: string): AppliedPatchChange[] {
|
|
160
452
|
if (!isApplyPatchDetails(details)) return [];
|
|
161
453
|
return coalesceAppliedPatchChangesForRendering(details.changes, cwd).toSorted((left, right) =>
|
|
162
|
-
comparePaths(
|
|
454
|
+
comparePaths(
|
|
455
|
+
resolve(cwd, left.kind === "move" ? left.sourcePath : left.path),
|
|
456
|
+
resolve(cwd, right.kind === "move" ? right.sourcePath : right.path),
|
|
457
|
+
),
|
|
163
458
|
);
|
|
164
459
|
}
|
|
165
460
|
|
|
@@ -231,6 +526,7 @@ function changeDiffLines(change: AppliedPatchChange): DiffLine[] {
|
|
|
231
526
|
.map((content, index) => ({ kind: "add", lineNumber: index + 1, content }));
|
|
232
527
|
}
|
|
233
528
|
if (change.kind === "delete") {
|
|
529
|
+
if (change.content === undefined) return [];
|
|
234
530
|
return change.content
|
|
235
531
|
.replace(/\n$/, "")
|
|
236
532
|
.split("\n")
|
|
@@ -334,6 +630,46 @@ function renderHeader(changes: readonly AppliedPatchChange[], theme: Theme, cwd:
|
|
|
334
630
|
return `${theme.fg("dim", "• ")}${theme.bold("Edited")} ${changes.length} ${noun} ${countSummary(additions, deletions, theme)}`;
|
|
335
631
|
}
|
|
336
632
|
|
|
633
|
+
function renderFailedChangeSummary(
|
|
634
|
+
details: ApplyPatchDetails,
|
|
635
|
+
changes: readonly AppliedPatchChange[],
|
|
636
|
+
theme: Theme,
|
|
637
|
+
cwd: string,
|
|
638
|
+
): string[] {
|
|
639
|
+
const summary = applyPatchSummaryPaths(details);
|
|
640
|
+
const paths = [
|
|
641
|
+
...summary.added.map((path) => ({ status: "A", path })),
|
|
642
|
+
...summary.modified.map((path) => ({ status: "M", path })),
|
|
643
|
+
...summary.deleted.map((path) => ({ status: "D", path })),
|
|
644
|
+
];
|
|
645
|
+
if (paths.length === 0) return [];
|
|
646
|
+
const additions = changes.reduce((total, change) => total + change.additions, 0);
|
|
647
|
+
const deletions = changes.reduce((total, change) => total + change.deletions, 0);
|
|
648
|
+
const noun = paths.length === 1 ? "file" : "files";
|
|
649
|
+
return [
|
|
650
|
+
`${theme.fg("dim", "• ")}${theme.bold("Changed")} ${paths.length} ${noun} ${countSummary(additions, deletions, theme)}`,
|
|
651
|
+
...paths.map(
|
|
652
|
+
({ status, path }) => ` ${theme.fg("dim", "└ ")}${status} ${displayPath(path, cwd)}`,
|
|
653
|
+
),
|
|
654
|
+
];
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function failedResultHasChangedFiles(details: ApplyPatchDetails): boolean {
|
|
658
|
+
const summary = applyPatchSummaryPaths(details);
|
|
659
|
+
return summary.added.length > 0 || summary.modified.length > 0 || summary.deleted.length > 0;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function failedResultHasNoChanges(details: ApplyPatchDetails): boolean {
|
|
663
|
+
const hasUnverifiedState = (details.instructions ?? []).some((instruction) =>
|
|
664
|
+
instruction.finalStates?.some((state) => state.state === "not-verified"),
|
|
665
|
+
);
|
|
666
|
+
return (
|
|
667
|
+
!failedResultHasChangedFiles(details) &&
|
|
668
|
+
!applyPatchHasOtherFilesystemChanges(details) &&
|
|
669
|
+
!hasUnverifiedState
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
|
|
337
673
|
function renderChange(
|
|
338
674
|
change: AppliedPatchChange,
|
|
339
675
|
width: number,
|
|
@@ -341,6 +677,7 @@ function renderChange(
|
|
|
341
677
|
palette: DiffPalette,
|
|
342
678
|
): string[] {
|
|
343
679
|
const lines = changeDiffLines(change);
|
|
680
|
+
if (change.kind === "move") return [];
|
|
344
681
|
const languagePath = change.kind === "update" && change.moveTo ? change.moveTo : change.path;
|
|
345
682
|
highlightDiffLines(lines, languagePath);
|
|
346
683
|
const lineNumberWidth = lines.reduce(
|
|
@@ -351,6 +688,95 @@ function renderChange(
|
|
|
351
688
|
return lines.flatMap((line) => renderDiffLine(line, width, lineNumberWidth, theme, palette));
|
|
352
689
|
}
|
|
353
690
|
|
|
691
|
+
function instructionStatusLabel(status: ApplyPatchInstructionStatus, theme: Theme): string {
|
|
692
|
+
const label = `[${formatApplyPatchInstructionStatusLabel(status)}]`;
|
|
693
|
+
switch (status) {
|
|
694
|
+
case "applied":
|
|
695
|
+
return theme.fg("success", label);
|
|
696
|
+
case "failed":
|
|
697
|
+
return theme.fg("error", label);
|
|
698
|
+
case "dead":
|
|
699
|
+
case "no-op":
|
|
700
|
+
case "not-run":
|
|
701
|
+
case "planned":
|
|
702
|
+
return theme.fg("dim", label);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function instructionLabel(instruction: ApplyPatchInstructionDetails, cwd: string): string {
|
|
707
|
+
const verb =
|
|
708
|
+
instruction.kind === "add"
|
|
709
|
+
? "Add"
|
|
710
|
+
: instruction.kind === "delete"
|
|
711
|
+
? "Delete"
|
|
712
|
+
: instruction.kind === "move"
|
|
713
|
+
? "Move"
|
|
714
|
+
: "Update";
|
|
715
|
+
const path = displayPath(instruction.path, cwd);
|
|
716
|
+
if (!instruction.moveTo) return `${verb} ${path}`;
|
|
717
|
+
return instruction.kind === "update"
|
|
718
|
+
? `Update & Move ${path} → ${displayPath(instruction.moveTo, cwd)}`
|
|
719
|
+
: `${verb} ${path} → ${displayPath(instruction.moveTo, cwd)}`;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function instructionChanges(
|
|
723
|
+
details: ApplyPatchDetails,
|
|
724
|
+
instructionIndex: number,
|
|
725
|
+
): AppliedPatchChange[] {
|
|
726
|
+
const instruction = details.instructions?.find(
|
|
727
|
+
(candidate) => candidate.index === instructionIndex,
|
|
728
|
+
);
|
|
729
|
+
return (instruction?.changeIndexes ?? []).flatMap((index) => {
|
|
730
|
+
const change = details.changes[index];
|
|
731
|
+
return change && !(instruction?.status === "failed" && change.kind === "move" && !change.exact)
|
|
732
|
+
? [change]
|
|
733
|
+
: [];
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function renderInstructionResults(
|
|
738
|
+
details: ApplyPatchDetails,
|
|
739
|
+
theme: Theme,
|
|
740
|
+
cwd: string,
|
|
741
|
+
expanded: boolean,
|
|
742
|
+
width?: number,
|
|
743
|
+
): string[] {
|
|
744
|
+
const lines: string[] = [];
|
|
745
|
+
const instructions = details.instructions ?? [];
|
|
746
|
+
if (!applyPatchNeedsInstructionResults(details, cwd)) return lines;
|
|
747
|
+
lines.push(theme.bold("Patch instruction results:"));
|
|
748
|
+
const palette = diffPalette(theme);
|
|
749
|
+
for (const instruction of instructions) {
|
|
750
|
+
const feedback = formatApplyPatchInstructionFeedback(instruction, details, cwd);
|
|
751
|
+
lines.push(
|
|
752
|
+
` ${instruction.index}. ${instructionStatusLabel(instruction.status, theme)} ${instructionLabel(instruction, cwd)}${feedback ? ` ${theme.fg("dim", `— ${feedback}`)}` : ""}`,
|
|
753
|
+
);
|
|
754
|
+
if (expanded && width !== undefined) {
|
|
755
|
+
const changes = instructionChanges(details, instruction.index);
|
|
756
|
+
const inset = Math.min(4, Math.max(0, width - 1));
|
|
757
|
+
const contentWidth = Math.max(1, width - inset);
|
|
758
|
+
for (const change of changes) {
|
|
759
|
+
lines.push(
|
|
760
|
+
...renderChange(change, contentWidth, theme, palette).map(
|
|
761
|
+
(line) => `${" ".repeat(inset)}${line}`,
|
|
762
|
+
),
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
} else if (expanded) {
|
|
766
|
+
for (const change of instructionChanges(details, instruction.index)) {
|
|
767
|
+
lines.push(
|
|
768
|
+
...changeDiffLines(change).map((line) => {
|
|
769
|
+
if (line.separator) return ` ${theme.fg("dim", "⋮")}`;
|
|
770
|
+
const sign = line.kind === "add" ? "+" : line.kind === "delete" ? "-" : " ";
|
|
771
|
+
return ` ${sign}${line.lineNumber ?? ""} ${line.content}`;
|
|
772
|
+
}),
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return lines;
|
|
778
|
+
}
|
|
779
|
+
|
|
354
780
|
export function formatApplyPatchRenderText(
|
|
355
781
|
details: ApplyPatchDetails,
|
|
356
782
|
theme: Theme,
|
|
@@ -358,27 +784,48 @@ export function formatApplyPatchRenderText(
|
|
|
358
784
|
): string {
|
|
359
785
|
const lines: string[] = [];
|
|
360
786
|
const changes = sortedChanges(details, cwd);
|
|
361
|
-
|
|
787
|
+
const showInstructionResults = applyPatchNeedsInstructionResults(details, cwd);
|
|
788
|
+
if (details.status === "failed") {
|
|
789
|
+
lines.push(...renderFailedChangeSummary(details, changes, theme, cwd));
|
|
790
|
+
} else if (changes.length > 0) {
|
|
362
791
|
lines.push(renderHeader(changes, theme, cwd));
|
|
363
792
|
for (const [index, change] of changes.entries()) {
|
|
364
793
|
if (changes.length > 1) {
|
|
365
794
|
lines.push(
|
|
366
|
-
` ${theme.fg("dim", "└ ")}${
|
|
795
|
+
` ${theme.fg("dim", "└ ")}${changeListPath(change, cwd)} ${countSummary(change.additions, change.deletions, theme)}`,
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
if (!showInstructionResults) {
|
|
799
|
+
lines.push(
|
|
800
|
+
...changeDiffLines(change).map((line) => {
|
|
801
|
+
if (line.separator) return ` ${theme.fg("dim", "⋮")}`;
|
|
802
|
+
const sign = line.kind === "add" ? "+" : line.kind === "delete" ? "-" : " ";
|
|
803
|
+
return ` ${sign}${line.lineNumber ?? ""} ${line.content}`;
|
|
804
|
+
}),
|
|
367
805
|
);
|
|
368
806
|
}
|
|
369
|
-
lines.push(
|
|
370
|
-
...changeDiffLines(change).map((line) => {
|
|
371
|
-
if (line.separator) return ` ${theme.fg("dim", "⋮")}`;
|
|
372
|
-
const sign = line.kind === "add" ? "+" : line.kind === "delete" ? "-" : " ";
|
|
373
|
-
return ` ${sign}${line.lineNumber ?? ""} ${line.content}`;
|
|
374
|
-
}),
|
|
375
|
-
);
|
|
376
807
|
if (index !== changes.length - 1) lines.push("");
|
|
377
808
|
}
|
|
378
809
|
}
|
|
379
810
|
if (details?.status === "failed") {
|
|
380
811
|
if (lines.length > 0) lines.push("");
|
|
381
812
|
lines.push(theme.bold(theme.fg("error", "✘ Failed to apply patch")));
|
|
813
|
+
lines.push(
|
|
814
|
+
...formatApplyPatchFailureHeading(details).map((line) => ` ${theme.fg("dim", line)}`),
|
|
815
|
+
);
|
|
816
|
+
if (failedResultHasNoChanges(details)) lines.push(" No files were changed.");
|
|
817
|
+
else if (
|
|
818
|
+
!failedResultHasChangedFiles(details) &&
|
|
819
|
+
applyPatchHasOtherFilesystemChanges(details)
|
|
820
|
+
) {
|
|
821
|
+
lines.push(" Filesystem changed.");
|
|
822
|
+
}
|
|
823
|
+
} else {
|
|
824
|
+
if (changes.length === 0) lines.push(theme.bold("Success. No files were changed."));
|
|
825
|
+
}
|
|
826
|
+
if (showInstructionResults) {
|
|
827
|
+
if (lines.length > 0) lines.push("");
|
|
828
|
+
lines.push(...renderInstructionResults(details, theme, cwd, true));
|
|
382
829
|
}
|
|
383
830
|
return lines.join("\n");
|
|
384
831
|
}
|
|
@@ -399,20 +846,25 @@ export class ApplyPatchDiffComponent implements Component {
|
|
|
399
846
|
render(width: number): string[] {
|
|
400
847
|
const effectiveWidth = Math.max(1, width);
|
|
401
848
|
const changes = sortedChanges(this.details, this.cwd);
|
|
402
|
-
const
|
|
849
|
+
const showInstructionResults = applyPatchNeedsInstructionResults(this.details, this.cwd);
|
|
403
850
|
const lines: string[] = [];
|
|
404
851
|
|
|
405
|
-
if (
|
|
852
|
+
if (this.details.status === "failed") {
|
|
853
|
+
for (const line of renderFailedChangeSummary(this.details, changes, this.theme, this.cwd)) {
|
|
854
|
+
lines.push(...wrapTextWithAnsi(line, effectiveWidth));
|
|
855
|
+
}
|
|
856
|
+
} else if (changes.length > 0) {
|
|
406
857
|
lines.push(...wrapTextWithAnsi(renderHeader(changes, this.theme, this.cwd), effectiveWidth));
|
|
407
858
|
for (const [index, change] of changes.entries()) {
|
|
408
859
|
if (this.expanded && index > 0) lines.push("");
|
|
409
860
|
if (changes.length > 1) {
|
|
410
|
-
const header = ` ${this.theme.fg("dim", "└ ")}${
|
|
861
|
+
const header = ` ${this.theme.fg("dim", "└ ")}${changeListPath(change, this.cwd)} ${countSummary(change.additions, change.deletions, this.theme)}`;
|
|
411
862
|
lines.push(...wrapTextWithAnsi(header, effectiveWidth));
|
|
412
863
|
}
|
|
413
|
-
if (this.expanded) {
|
|
864
|
+
if (this.expanded && !showInstructionResults) {
|
|
414
865
|
const inset = Math.min(4, Math.max(0, effectiveWidth - 1));
|
|
415
866
|
const contentWidth = Math.max(1, effectiveWidth - inset);
|
|
867
|
+
const palette = diffPalette(this.theme);
|
|
416
868
|
lines.push(
|
|
417
869
|
...renderChange(change, contentWidth, this.theme, palette).map(
|
|
418
870
|
(line) => `${" ".repeat(inset)}${line}`,
|
|
@@ -425,12 +877,39 @@ export class ApplyPatchDiffComponent implements Component {
|
|
|
425
877
|
if (this.details?.status === "failed") {
|
|
426
878
|
if (lines.length > 0) lines.push("");
|
|
427
879
|
lines.push(
|
|
428
|
-
|
|
880
|
+
...wrapTextWithAnsi(
|
|
429
881
|
this.theme.bold(this.theme.fg("error", "✘ Failed to apply patch")),
|
|
430
882
|
effectiveWidth,
|
|
431
|
-
"",
|
|
432
883
|
),
|
|
433
884
|
);
|
|
885
|
+
for (const line of formatApplyPatchFailureHeading(this.details)) {
|
|
886
|
+
lines.push(...wrapTextWithAnsi(` ${this.theme.fg("dim", line)}`, effectiveWidth));
|
|
887
|
+
}
|
|
888
|
+
if (failedResultHasNoChanges(this.details)) {
|
|
889
|
+
lines.push(...wrapTextWithAnsi(" No files were changed.", effectiveWidth));
|
|
890
|
+
} else if (
|
|
891
|
+
!failedResultHasChangedFiles(this.details) &&
|
|
892
|
+
applyPatchHasOtherFilesystemChanges(this.details)
|
|
893
|
+
) {
|
|
894
|
+
lines.push(...wrapTextWithAnsi(" Filesystem changed.", effectiveWidth));
|
|
895
|
+
}
|
|
896
|
+
} else if (changes.length === 0) {
|
|
897
|
+
lines.push(
|
|
898
|
+
...wrapTextWithAnsi(this.theme.bold("Success. No files were changed."), effectiveWidth),
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if (showInstructionResults) {
|
|
903
|
+
if (lines.length > 0) lines.push("");
|
|
904
|
+
for (const line of renderInstructionResults(
|
|
905
|
+
this.details,
|
|
906
|
+
this.theme,
|
|
907
|
+
this.cwd,
|
|
908
|
+
this.expanded,
|
|
909
|
+
effectiveWidth,
|
|
910
|
+
)) {
|
|
911
|
+
lines.push(...wrapTextWithAnsi(line, effectiveWidth));
|
|
912
|
+
}
|
|
434
913
|
}
|
|
435
914
|
|
|
436
915
|
return lines;
|