pi-openai-codex-compat 0.0.1-alpha.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/CHANGELOG.md +64 -0
- package/LICENSE +20 -0
- package/LICENSES/Apache-2.0.txt +201 -0
- package/LICENSES/pi-ai-MIT.txt +21 -0
- package/README.md +331 -0
- package/THIRD_PARTY_NOTICES.md +21 -0
- package/extensions/openai-codex-compat/apply-patch-diff-render.ts +436 -0
- package/extensions/openai-codex-compat/apply-patch-engine.ts +1004 -0
- package/extensions/openai-codex-compat/apply-patch-render.ts +133 -0
- package/extensions/openai-codex-compat/apply-patch.ts +142 -0
- package/extensions/openai-codex-compat/codex-protocol.ts +598 -0
- package/extensions/openai-codex-compat/codex-provider.ts +740 -0
- package/extensions/openai-codex-compat/codex-stream.ts +444 -0
- package/extensions/openai-codex-compat/codex-tool-surface.ts +186 -0
- package/extensions/openai-codex-compat/codex-transport.ts +855 -0
- package/extensions/openai-codex-compat/compaction-checkpoint.ts +304 -0
- package/extensions/openai-codex-compat/config.ts +268 -0
- package/extensions/openai-codex-compat/footer.ts +99 -0
- package/extensions/openai-codex-compat/image-generation-render.ts +166 -0
- package/extensions/openai-codex-compat/image-generation.ts +355 -0
- package/extensions/openai-codex-compat/index.ts +65 -0
- package/extensions/openai-codex-compat/model-policy.ts +67 -0
- package/extensions/openai-codex-compat/namespaced-tools.ts +43 -0
- package/extensions/openai-codex-compat/native-history.ts +78 -0
- package/extensions/openai-codex-compat/remote-compaction.ts +198 -0
- package/extensions/openai-codex-compat/request-options.ts +121 -0
- package/extensions/openai-codex-compat/responses-replay.ts +33 -0
- package/extensions/openai-codex-compat/settings-pane.ts +298 -0
- package/extensions/openai-codex-compat/tool-runtime.ts +32 -0
- package/extensions/openai-codex-compat/tools.ts +70 -0
- package/extensions/openai-codex-compat/vendor/pi-ai/README.md +15 -0
- package/extensions/openai-codex-compat/vendor/pi-ai/openai-responses-serialization.ts +660 -0
- package/extensions/openai-codex-compat/web-run-description.txt +105 -0
- package/extensions/openai-codex-compat/web-run-output.ts +172 -0
- package/extensions/openai-codex-compat/web-run-render.ts +681 -0
- package/extensions/openai-codex-compat/web-run-schema.ts +301 -0
- package/extensions/openai-codex-compat/web-run.ts +164 -0
- package/package.json +63 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { getLanguageFromPath, highlightCode, type Theme } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import {
|
|
5
|
+
type Component,
|
|
6
|
+
truncateToWidth,
|
|
7
|
+
visibleWidth,
|
|
8
|
+
wrapTextWithAnsi,
|
|
9
|
+
} from "@earendil-works/pi-tui";
|
|
10
|
+
import type { AppliedPatchChange, ApplyPatchDetails } from "./apply-patch-engine.ts";
|
|
11
|
+
import { usesLightToolPalette } from "./codex-tool-surface.ts";
|
|
12
|
+
|
|
13
|
+
type DiffLineKind = "add" | "delete" | "context";
|
|
14
|
+
|
|
15
|
+
type DiffLine = {
|
|
16
|
+
kind: DiffLineKind;
|
|
17
|
+
lineNumber?: number;
|
|
18
|
+
content: string;
|
|
19
|
+
separator?: boolean;
|
|
20
|
+
highlighted?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type DiffPalette = {
|
|
24
|
+
light: boolean;
|
|
25
|
+
addLineBg: string;
|
|
26
|
+
deleteLineBg: string;
|
|
27
|
+
addGutterBg: string;
|
|
28
|
+
deleteGutterBg: string;
|
|
29
|
+
gutterFg: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const ANSI_RESET_BACKGROUND = "\u001b[49m";
|
|
33
|
+
const ANSI_DIM = "\u001b[2m";
|
|
34
|
+
const ANSI_NORMAL_INTENSITY = "\u001b[22m";
|
|
35
|
+
|
|
36
|
+
const DARK_TRUECOLOR_ADD_BG = "\u001b[48;2;33;58;43m";
|
|
37
|
+
const DARK_TRUECOLOR_DELETE_BG = "\u001b[48;2;74;34;29m";
|
|
38
|
+
const LIGHT_TRUECOLOR_ADD_BG = "\u001b[48;2;218;251;225m";
|
|
39
|
+
const LIGHT_TRUECOLOR_DELETE_BG = "\u001b[48;2;255;235;233m";
|
|
40
|
+
const LIGHT_TRUECOLOR_ADD_GUTTER_BG = "\u001b[48;2;172;238;187m";
|
|
41
|
+
const LIGHT_TRUECOLOR_DELETE_GUTTER_BG = "\u001b[48;2;255;206;203m";
|
|
42
|
+
const LIGHT_TRUECOLOR_GUTTER_FG = "\u001b[38;2;31;35;40m";
|
|
43
|
+
|
|
44
|
+
const DARK_256_ADD_BG = "\u001b[48;5;22m";
|
|
45
|
+
const DARK_256_DELETE_BG = "\u001b[48;5;52m";
|
|
46
|
+
const LIGHT_256_ADD_BG = "\u001b[48;5;194m";
|
|
47
|
+
const LIGHT_256_DELETE_BG = "\u001b[48;5;224m";
|
|
48
|
+
const LIGHT_256_ADD_GUTTER_BG = "\u001b[48;5;157m";
|
|
49
|
+
const LIGHT_256_DELETE_GUTTER_BG = "\u001b[48;5;217m";
|
|
50
|
+
const LIGHT_256_GUTTER_FG = "\u001b[38;5;236m";
|
|
51
|
+
const BACKGROUND_RESET_PATTERN = new RegExp(String.raw`\u001b\[(?:0|49)m`, "g");
|
|
52
|
+
|
|
53
|
+
function relativePathWithin(basePath: string, targetPath: string): string | undefined {
|
|
54
|
+
const path = relative(resolve(basePath), targetPath);
|
|
55
|
+
if (path === "") return path;
|
|
56
|
+
if (path === ".." || path.startsWith(`..${sep}`) || isAbsolute(path)) return undefined;
|
|
57
|
+
return path;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function displayPath(path: string, cwd: string): string {
|
|
61
|
+
const absolutePath = isAbsolute(path) ? resolve(path) : resolve(cwd, path);
|
|
62
|
+
const cwdRelativePath = relativePathWithin(cwd, absolutePath);
|
|
63
|
+
if (cwdRelativePath !== undefined) return cwdRelativePath || ".";
|
|
64
|
+
|
|
65
|
+
const homeRelativePath = relativePathWithin(homedir(), absolutePath);
|
|
66
|
+
if (homeRelativePath !== undefined) return homeRelativePath ? join("~", homeRelativePath) : "~";
|
|
67
|
+
|
|
68
|
+
return absolutePath;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function changePath(change: AppliedPatchChange, cwd: string): string {
|
|
72
|
+
const path = displayPath(change.path, cwd);
|
|
73
|
+
return change.kind === "update" && change.moveTo
|
|
74
|
+
? `${path} → ${displayPath(change.moveTo, cwd)}`
|
|
75
|
+
: path;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function changeVerb(change: AppliedPatchChange): string {
|
|
79
|
+
switch (change.kind) {
|
|
80
|
+
case "add":
|
|
81
|
+
return "Added";
|
|
82
|
+
case "delete":
|
|
83
|
+
return "Deleted";
|
|
84
|
+
case "update":
|
|
85
|
+
return "Edited";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function countSummary(additions: number, deletions: number, theme: Theme): string {
|
|
90
|
+
return `(${theme.fg("success", `+${additions}`)} ${theme.fg("error", `-${deletions}`)})`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function comparePaths(left: string, right: string): number {
|
|
94
|
+
if (process.platform === "win32") return left < right ? -1 : left > right ? 1 : 0;
|
|
95
|
+
return Buffer.compare(Buffer.from(left), Buffer.from(right));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isStringArray(value: unknown): value is string[] {
|
|
99
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isAppliedPatchChange(value: unknown): value is AppliedPatchChange {
|
|
103
|
+
if (typeof value !== "object" || value === null) return false;
|
|
104
|
+
const change = value as {
|
|
105
|
+
kind?: unknown;
|
|
106
|
+
path?: unknown;
|
|
107
|
+
moveTo?: unknown;
|
|
108
|
+
content?: unknown;
|
|
109
|
+
oldContent?: unknown;
|
|
110
|
+
newContent?: unknown;
|
|
111
|
+
displayDiff?: unknown;
|
|
112
|
+
additions?: unknown;
|
|
113
|
+
deletions?: unknown;
|
|
114
|
+
};
|
|
115
|
+
if (
|
|
116
|
+
typeof change.path !== "string" ||
|
|
117
|
+
typeof change.displayDiff !== "string" ||
|
|
118
|
+
typeof change.additions !== "number" ||
|
|
119
|
+
typeof change.deletions !== "number"
|
|
120
|
+
) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
if (change.kind === "add" || change.kind === "delete") {
|
|
124
|
+
return typeof change.content === "string";
|
|
125
|
+
}
|
|
126
|
+
return (
|
|
127
|
+
change.kind === "update" &&
|
|
128
|
+
typeof change.oldContent === "string" &&
|
|
129
|
+
typeof change.newContent === "string" &&
|
|
130
|
+
(change.moveTo === undefined || typeof change.moveTo === "string")
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function isApplyPatchDetails(value: unknown): value is ApplyPatchDetails {
|
|
135
|
+
if (typeof value !== "object" || value === null) return false;
|
|
136
|
+
const details = value as {
|
|
137
|
+
status?: unknown;
|
|
138
|
+
exact?: unknown;
|
|
139
|
+
changes?: unknown;
|
|
140
|
+
added?: unknown;
|
|
141
|
+
modified?: unknown;
|
|
142
|
+
deleted?: unknown;
|
|
143
|
+
};
|
|
144
|
+
return (
|
|
145
|
+
(details.status === "completed" || details.status === "failed") &&
|
|
146
|
+
typeof details.exact === "boolean" &&
|
|
147
|
+
Array.isArray(details.changes) &&
|
|
148
|
+
details.changes.every(isAppliedPatchChange) &&
|
|
149
|
+
isStringArray(details.added) &&
|
|
150
|
+
isStringArray(details.modified) &&
|
|
151
|
+
isStringArray(details.deleted)
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function sortedChanges(details: ApplyPatchDetails, cwd: string): AppliedPatchChange[] {
|
|
156
|
+
if (!isApplyPatchDetails(details)) return [];
|
|
157
|
+
return details.changes.toSorted((left, right) =>
|
|
158
|
+
comparePaths(resolve(cwd, left.path), resolve(cwd, right.path)),
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function diffPalette(theme: Theme): DiffPalette {
|
|
163
|
+
const light = usesLightToolPalette(theme);
|
|
164
|
+
const truecolor = theme.getColorMode?.() !== "256color";
|
|
165
|
+
if (truecolor) {
|
|
166
|
+
return {
|
|
167
|
+
light,
|
|
168
|
+
addLineBg: light ? LIGHT_TRUECOLOR_ADD_BG : DARK_TRUECOLOR_ADD_BG,
|
|
169
|
+
deleteLineBg: light ? LIGHT_TRUECOLOR_DELETE_BG : DARK_TRUECOLOR_DELETE_BG,
|
|
170
|
+
addGutterBg: light ? LIGHT_TRUECOLOR_ADD_GUTTER_BG : DARK_TRUECOLOR_ADD_BG,
|
|
171
|
+
deleteGutterBg: light ? LIGHT_TRUECOLOR_DELETE_GUTTER_BG : DARK_TRUECOLOR_DELETE_BG,
|
|
172
|
+
gutterFg: light ? LIGHT_TRUECOLOR_GUTTER_FG : "",
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
light,
|
|
177
|
+
addLineBg: light ? LIGHT_256_ADD_BG : DARK_256_ADD_BG,
|
|
178
|
+
deleteLineBg: light ? LIGHT_256_DELETE_BG : DARK_256_DELETE_BG,
|
|
179
|
+
addGutterBg: light ? LIGHT_256_ADD_GUTTER_BG : DARK_256_ADD_BG,
|
|
180
|
+
deleteGutterBg: light ? LIGHT_256_DELETE_GUTTER_BG : DARK_256_DELETE_BG,
|
|
181
|
+
gutterFg: light ? LIGHT_256_GUTTER_FG : "",
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function lineBackground(kind: DiffLineKind, palette: DiffPalette): string {
|
|
186
|
+
if (kind === "add") return palette.addLineBg;
|
|
187
|
+
if (kind === "delete") return palette.deleteLineBg;
|
|
188
|
+
return "";
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function gutterBackground(kind: DiffLineKind, palette: DiffPalette): string {
|
|
192
|
+
if (kind === "add") return palette.addGutterBg;
|
|
193
|
+
if (kind === "delete") return palette.deleteGutterBg;
|
|
194
|
+
return "";
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function restoreBackgroundAfterResets(text: string, background: string): string {
|
|
198
|
+
if (!background) return text;
|
|
199
|
+
return text.replace(BACKGROUND_RESET_PATTERN, (reset) => `${reset}${background}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function withBackground(text: string, background: string): string {
|
|
203
|
+
if (!background) return text;
|
|
204
|
+
return `${background}${restoreBackgroundAfterResets(text, background)}${ANSI_RESET_BACKGROUND}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function parseDisplayDiff(displayDiff: string): DiffLine[] {
|
|
208
|
+
return displayDiff.split("\n").map((line) => {
|
|
209
|
+
if (/^\s+\.\.\.$/.test(line)) {
|
|
210
|
+
return { kind: "context", content: "⋮", separator: true };
|
|
211
|
+
}
|
|
212
|
+
const match = line.match(/^([+\-\s])\s*(\d+)\s(.*)$/);
|
|
213
|
+
if (!match) return { kind: "context", content: line };
|
|
214
|
+
return {
|
|
215
|
+
kind: match[1] === "+" ? "add" : match[1] === "-" ? "delete" : "context",
|
|
216
|
+
lineNumber: Number(match[2]),
|
|
217
|
+
content: match[3] ?? "",
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function changeDiffLines(change: AppliedPatchChange): DiffLine[] {
|
|
223
|
+
if (change.kind === "add") {
|
|
224
|
+
return change.content
|
|
225
|
+
.replace(/\n$/, "")
|
|
226
|
+
.split("\n")
|
|
227
|
+
.map((content, index) => ({ kind: "add", lineNumber: index + 1, content }));
|
|
228
|
+
}
|
|
229
|
+
if (change.kind === "delete") {
|
|
230
|
+
return change.content
|
|
231
|
+
.replace(/\n$/, "")
|
|
232
|
+
.split("\n")
|
|
233
|
+
.map((content, index) => ({ kind: "delete", lineNumber: index + 1, content }));
|
|
234
|
+
}
|
|
235
|
+
return parseDisplayDiff(change.displayDiff);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function highlightDiffLines(lines: DiffLine[], filePath: string): void {
|
|
239
|
+
const language = getLanguageFromPath(filePath);
|
|
240
|
+
if (!language) return;
|
|
241
|
+
|
|
242
|
+
let blockStart = 0;
|
|
243
|
+
while (blockStart < lines.length) {
|
|
244
|
+
while (lines[blockStart]?.separator) blockStart += 1;
|
|
245
|
+
if (blockStart >= lines.length) break;
|
|
246
|
+
let blockEnd = blockStart;
|
|
247
|
+
while (blockEnd < lines.length && !lines[blockEnd]?.separator) blockEnd += 1;
|
|
248
|
+
const block = lines.slice(blockStart, blockEnd);
|
|
249
|
+
const highlighted = highlightCode(
|
|
250
|
+
block.map((line) => line.content.replace(/\t/g, " ")).join("\n"),
|
|
251
|
+
language,
|
|
252
|
+
);
|
|
253
|
+
for (const [index, line] of block.entries()) {
|
|
254
|
+
const highlightedLine = highlighted[index];
|
|
255
|
+
if (highlightedLine !== undefined) line.highlighted = highlightedLine;
|
|
256
|
+
}
|
|
257
|
+
blockStart = blockEnd + 1;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function styleContent(line: DiffLine, theme: Theme): string {
|
|
262
|
+
const content = line.highlighted ?? line.content.replace(/\t/g, " ");
|
|
263
|
+
if (line.highlighted) {
|
|
264
|
+
return line.kind === "delete" ? `${ANSI_DIM}${content}${ANSI_NORMAL_INTENSITY}` : content;
|
|
265
|
+
}
|
|
266
|
+
if (line.kind === "add") return theme.fg("toolDiffAdded", content);
|
|
267
|
+
if (line.kind === "delete") return theme.fg("toolDiffRemoved", content);
|
|
268
|
+
return theme.fg("toolDiffContext", content);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function fillLine(line: string, width: number, background: string): string {
|
|
272
|
+
const truncated = truncateToWidth(line, width, "");
|
|
273
|
+
const padding = " ".repeat(Math.max(0, width - visibleWidth(truncated)));
|
|
274
|
+
return withBackground(`${truncated}${padding}`, background);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function renderDiffLine(
|
|
278
|
+
line: DiffLine,
|
|
279
|
+
width: number,
|
|
280
|
+
lineNumberWidth: number,
|
|
281
|
+
theme: Theme,
|
|
282
|
+
palette: DiffPalette,
|
|
283
|
+
): string[] {
|
|
284
|
+
const gutterWidth = Math.max(1, lineNumberWidth);
|
|
285
|
+
if (line.separator) {
|
|
286
|
+
const gutter = " ".repeat(gutterWidth + 2);
|
|
287
|
+
return [fillLine(`${gutter}${theme.fg("dim", line.content)}`, width, "")];
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const contentWidth = Math.max(1, width - gutterWidth - 2);
|
|
291
|
+
const contentLines = wrapTextWithAnsi(styleContent(line, theme), contentWidth);
|
|
292
|
+
const kindBackground = lineBackground(line.kind, palette);
|
|
293
|
+
|
|
294
|
+
return contentLines.map((content, index) => {
|
|
295
|
+
const lineNumber =
|
|
296
|
+
index === 0 && line.lineNumber !== undefined
|
|
297
|
+
? String(line.lineNumber).padStart(gutterWidth)
|
|
298
|
+
: " ".repeat(gutterWidth);
|
|
299
|
+
const sign =
|
|
300
|
+
index === 0 ? (line.kind === "add" ? "+" : line.kind === "delete" ? "-" : " ") : " ";
|
|
301
|
+
const rawGutter = `${lineNumber} `;
|
|
302
|
+
const gutter =
|
|
303
|
+
line.kind === "context"
|
|
304
|
+
? theme.fg("dim", rawGutter)
|
|
305
|
+
: palette.light
|
|
306
|
+
? `${palette.gutterFg}${rawGutter}\u001b[39m`
|
|
307
|
+
: `${ANSI_DIM}${rawGutter}${ANSI_NORMAL_INTENSITY}`;
|
|
308
|
+
const styledGutter = withBackground(gutter, gutterBackground(line.kind, palette));
|
|
309
|
+
const styledSign =
|
|
310
|
+
line.kind === "add"
|
|
311
|
+
? theme.fg("toolDiffAdded", sign)
|
|
312
|
+
: line.kind === "delete"
|
|
313
|
+
? theme.fg("toolDiffRemoved", sign)
|
|
314
|
+
: sign;
|
|
315
|
+
const body = `${styledSign}${content}`;
|
|
316
|
+
const visible = visibleWidth(rawGutter) + visibleWidth(body);
|
|
317
|
+
const padding = " ".repeat(Math.max(0, width - visible));
|
|
318
|
+
return `${styledGutter}${withBackground(`${body}${padding}`, kindBackground)}`;
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function renderHeader(changes: readonly AppliedPatchChange[], theme: Theme, cwd: string): string {
|
|
323
|
+
const additions = changes.reduce((total, change) => total + change.additions, 0);
|
|
324
|
+
const deletions = changes.reduce((total, change) => total + change.deletions, 0);
|
|
325
|
+
if (changes.length === 1) {
|
|
326
|
+
const change = changes[0]!;
|
|
327
|
+
return `${theme.fg("dim", "• ")}${theme.bold(changeVerb(change))} ${changePath(change, cwd)} ${countSummary(change.additions, change.deletions, theme)}`;
|
|
328
|
+
}
|
|
329
|
+
const noun = changes.length === 1 ? "file" : "files";
|
|
330
|
+
return `${theme.fg("dim", "• ")}${theme.bold("Edited")} ${changes.length} ${noun} ${countSummary(additions, deletions, theme)}`;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function renderChange(
|
|
334
|
+
change: AppliedPatchChange,
|
|
335
|
+
width: number,
|
|
336
|
+
theme: Theme,
|
|
337
|
+
palette: DiffPalette,
|
|
338
|
+
): string[] {
|
|
339
|
+
const lines = changeDiffLines(change);
|
|
340
|
+
const languagePath = change.kind === "update" && change.moveTo ? change.moveTo : change.path;
|
|
341
|
+
highlightDiffLines(lines, languagePath);
|
|
342
|
+
const lineNumberWidth = lines.reduce(
|
|
343
|
+
(maximum, line) =>
|
|
344
|
+
line.lineNumber === undefined ? maximum : Math.max(maximum, String(line.lineNumber).length),
|
|
345
|
+
1,
|
|
346
|
+
);
|
|
347
|
+
return lines.flatMap((line) => renderDiffLine(line, width, lineNumberWidth, theme, palette));
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function formatApplyPatchRenderText(
|
|
351
|
+
details: ApplyPatchDetails,
|
|
352
|
+
theme: Theme,
|
|
353
|
+
cwd = process.cwd(),
|
|
354
|
+
): string {
|
|
355
|
+
const lines: string[] = [];
|
|
356
|
+
const changes = sortedChanges(details, cwd);
|
|
357
|
+
if (changes.length > 0) {
|
|
358
|
+
lines.push(renderHeader(changes, theme, cwd));
|
|
359
|
+
for (const [index, change] of changes.entries()) {
|
|
360
|
+
if (changes.length > 1) {
|
|
361
|
+
lines.push(
|
|
362
|
+
` ${theme.fg("dim", "└ ")}${changePath(change, cwd)} ${countSummary(change.additions, change.deletions, theme)}`,
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
lines.push(
|
|
366
|
+
...changeDiffLines(change).map((line) => {
|
|
367
|
+
if (line.separator) return ` ${theme.fg("dim", "⋮")}`;
|
|
368
|
+
const sign = line.kind === "add" ? "+" : line.kind === "delete" ? "-" : " ";
|
|
369
|
+
return ` ${sign}${line.lineNumber ?? ""} ${line.content}`;
|
|
370
|
+
}),
|
|
371
|
+
);
|
|
372
|
+
if (index !== changes.length - 1) lines.push("");
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (details?.status === "failed") {
|
|
376
|
+
if (lines.length > 0) lines.push("");
|
|
377
|
+
lines.push(theme.bold(theme.fg("error", "✘ Failed to apply patch")));
|
|
378
|
+
}
|
|
379
|
+
return lines.join("\n");
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export class ApplyPatchDiffComponent implements Component {
|
|
383
|
+
private readonly details: ApplyPatchDetails;
|
|
384
|
+
private readonly theme: Theme;
|
|
385
|
+
private readonly cwd: string;
|
|
386
|
+
private readonly expanded: boolean;
|
|
387
|
+
|
|
388
|
+
constructor(details: ApplyPatchDetails, theme: Theme, cwd: string, expanded: boolean) {
|
|
389
|
+
this.details = details;
|
|
390
|
+
this.theme = theme;
|
|
391
|
+
this.cwd = cwd;
|
|
392
|
+
this.expanded = expanded;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
render(width: number): string[] {
|
|
396
|
+
const effectiveWidth = Math.max(1, width);
|
|
397
|
+
const changes = sortedChanges(this.details, this.cwd);
|
|
398
|
+
const palette = diffPalette(this.theme);
|
|
399
|
+
const lines: string[] = [];
|
|
400
|
+
|
|
401
|
+
if (changes.length > 0) {
|
|
402
|
+
lines.push(...wrapTextWithAnsi(renderHeader(changes, this.theme, this.cwd), effectiveWidth));
|
|
403
|
+
for (const [index, change] of changes.entries()) {
|
|
404
|
+
if (this.expanded && index > 0) lines.push("");
|
|
405
|
+
if (changes.length > 1) {
|
|
406
|
+
const header = ` ${this.theme.fg("dim", "└ ")}${changePath(change, this.cwd)} ${countSummary(change.additions, change.deletions, this.theme)}`;
|
|
407
|
+
lines.push(...wrapTextWithAnsi(header, effectiveWidth));
|
|
408
|
+
}
|
|
409
|
+
if (this.expanded) {
|
|
410
|
+
const inset = Math.min(4, Math.max(0, effectiveWidth - 1));
|
|
411
|
+
const contentWidth = Math.max(1, effectiveWidth - inset);
|
|
412
|
+
lines.push(
|
|
413
|
+
...renderChange(change, contentWidth, this.theme, palette).map(
|
|
414
|
+
(line) => `${" ".repeat(inset)}${line}`,
|
|
415
|
+
),
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (this.details?.status === "failed") {
|
|
422
|
+
if (lines.length > 0) lines.push("");
|
|
423
|
+
lines.push(
|
|
424
|
+
truncateToWidth(
|
|
425
|
+
this.theme.bold(this.theme.fg("error", "✘ Failed to apply patch")),
|
|
426
|
+
effectiveWidth,
|
|
427
|
+
"",
|
|
428
|
+
),
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return lines;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
invalidate(): void {}
|
|
436
|
+
}
|