pi-one-ui 0.1.1
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/LICENSE +21 -0
- package/README.md +106 -0
- package/extensions/app/config/renderer.ts +452 -0
- package/extensions/app/config/shell.ts +2484 -0
- package/extensions/app/ownership.ts +18 -0
- package/extensions/app/panel.ts +533 -0
- package/extensions/app/presets.ts +36 -0
- package/extensions/features/agent-summary/core.ts +194 -0
- package/extensions/features/agent-summary/index.ts +37 -0
- package/extensions/features/compact-thinking.ts +1262 -0
- package/extensions/features/context.ts +857 -0
- package/extensions/features/reference/index.ts +643 -0
- package/extensions/features/reference/session.ts +278 -0
- package/extensions/features/reference/subagent.ts +218 -0
- package/extensions/features/shell/aliases.ts +28 -0
- package/extensions/features/shell/flush-docked-bash.ts +39 -0
- package/extensions/features/shell/startup-header.ts +357 -0
- package/extensions/features/shell/working-message.ts +226 -0
- package/extensions/index.ts +52 -0
- package/extensions/shell/accent-rail-editor.ts +112 -0
- package/extensions/shell/accent-rail-layout-patch.ts +530 -0
- package/extensions/shell/completion-menu.ts +186 -0
- package/extensions/shell/editor-metadata-format.ts +287 -0
- package/extensions/shell/editor-transfer.ts +73 -0
- package/extensions/shell/extension-status.ts +102 -0
- package/extensions/shell/footer-format.ts +388 -0
- package/extensions/shell/footer-layout.ts +144 -0
- package/extensions/shell/footer.ts +1035 -0
- package/extensions/shell/format.ts +577 -0
- package/extensions/shell/git.ts +434 -0
- package/extensions/shell/icons.ts +261 -0
- package/extensions/shell/index.ts +1659 -0
- package/extensions/shell/interaction-summary.ts +1194 -0
- package/extensions/shell/live-context.ts +82 -0
- package/extensions/shell/minimalist-editor.ts +548 -0
- package/extensions/shell/package-version.ts +675 -0
- package/extensions/shell/project-refresh.ts +130 -0
- package/extensions/shell/project-state.ts +60 -0
- package/extensions/shell/prototype-patch-registry.ts +183 -0
- package/extensions/shell/runtime.ts +915 -0
- package/extensions/shell/selector-border.ts +109 -0
- package/extensions/shell/session-lifecycle.ts +60 -0
- package/extensions/shell/settings-command.ts +2144 -0
- package/extensions/shell/settings-previews.ts +137 -0
- package/extensions/shell/state.ts +83 -0
- package/extensions/shell/style.ts +480 -0
- package/extensions/shell/telemetry.ts +90 -0
- package/extensions/shell/ui.ts +1326 -0
- package/extensions/shell/user-message-osc.ts +279 -0
- package/extensions/shell/user-message-styles.ts +266 -0
- package/extensions/shell/user-message.ts +226 -0
- package/extensions/shell/working-line-messages.ts +24 -0
- package/extensions/shell/working-line-spinners.ts +54 -0
- package/extensions/shell/working-line.ts +1603 -0
- package/extensions/tools/ansi-color.ts +44 -0
- package/extensions/tools/ansi-text.ts +52 -0
- package/extensions/tools/component-tree.ts +37 -0
- package/extensions/tools/format.ts +28 -0
- package/extensions/tools/fullscreen-detect.ts +11 -0
- package/extensions/tools/patch-keys.ts +158 -0
- package/extensions/tools/sgr-mouse.ts +59 -0
- package/extensions/tools/tool-loading-icon.ts +20 -0
- package/extensions/tools/tool-result-sanitize.ts +27 -0
- package/extensions/transcript/index.ts +65 -0
- package/extensions/transcript/renderer/compact-mode.ts +1443 -0
- package/extensions/transcript/renderer/default-mode.ts +735 -0
- package/extensions/transcript/renderer/index.ts +286 -0
- package/extensions/transcript/renderer/markdown-enhance.ts +325 -0
- package/extensions/transcript/renderer/mouse/hover.ts +175 -0
- package/extensions/transcript/renderer/mouse/interaction.ts +1016 -0
- package/extensions/transcript/renderer/mouse/layout.ts +207 -0
- package/extensions/transcript/renderer/mouse/packets.ts +96 -0
- package/extensions/transcript/renderer/mouse/scroll.ts +211 -0
- package/extensions/transcript/renderer/tool/diff/ATTRIBUTION.md +28 -0
- package/extensions/transcript/renderer/tool/diff/ansi-utils.ts +128 -0
- package/extensions/transcript/renderer/tool/diff/diff-component.ts +112 -0
- package/extensions/transcript/renderer/tool/diff/diff-edit-render.ts +255 -0
- package/extensions/transcript/renderer/tool/diff/diff-header.ts +184 -0
- package/extensions/transcript/renderer/tool/diff/diff-highlight.ts +131 -0
- package/extensions/transcript/renderer/tool/diff/diff-inline.ts +350 -0
- package/extensions/transcript/renderer/tool/diff/diff-layout.ts +870 -0
- package/extensions/transcript/renderer/tool/diff/diff-limits.ts +189 -0
- package/extensions/transcript/renderer/tool/diff/diff-palette.ts +436 -0
- package/extensions/transcript/renderer/tool/diff/diff-parse.ts +536 -0
- package/extensions/transcript/renderer/tool/diff/diff-presentation.ts +79 -0
- package/extensions/transcript/renderer/tool/diff/diff-renderer.ts +29 -0
- package/extensions/transcript/renderer/tool/diff/diff-text.ts +57 -0
- package/extensions/transcript/renderer/tool/diff/diff-write-render.ts +629 -0
- package/extensions/transcript/renderer/tool/diff/index.ts +147 -0
- package/extensions/transcript/renderer/tool/diff/line-width-safety.ts +100 -0
- package/extensions/transcript/renderer/tool/diff/shiki-highlight.ts +104 -0
- package/extensions/transcript/renderer/tool/diff/write-execution.ts +124 -0
- package/extensions/transcript/renderer/tool/grouping.ts +619 -0
- package/extensions/transcript/renderer/tool/message-display.ts +183 -0
- package/extensions/transcript/renderer/tool/names.ts +195 -0
- package/extensions/transcript/renderer/tool/result.ts +842 -0
- package/extensions/transcript/renderer/tool/show-more-hint.ts +42 -0
- package/extensions/transcript/renderer/transcript-refresh.ts +79 -0
- package/package.json +58 -0
- package/themes/cc-dark.json +91 -0
- package/themes/cc-light.json +91 -0
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Text,
|
|
3
|
+
truncateToWidth,
|
|
4
|
+
visibleWidth,
|
|
5
|
+
type Component,
|
|
6
|
+
} from "@earendil-works/pi-tui";
|
|
7
|
+
import { sanitizeToolResultText } from "../../../../tools/tool-result-sanitize.ts";
|
|
8
|
+
import { showMoreHintText } from "../show-more-hint.ts";
|
|
9
|
+
import { splitWriteContentLines } from "./diff-text.ts";
|
|
10
|
+
import {
|
|
11
|
+
collectDiffStats,
|
|
12
|
+
getLineNumberWidth,
|
|
13
|
+
type DiffLineEntry,
|
|
14
|
+
type DiffStats,
|
|
15
|
+
type ParsedDiffEntry,
|
|
16
|
+
} from "./diff-parse.ts";
|
|
17
|
+
import {
|
|
18
|
+
buildInlineHighlightMap,
|
|
19
|
+
buildSplitRows,
|
|
20
|
+
type DiffSpan,
|
|
21
|
+
type SplitDiffRow,
|
|
22
|
+
} from "./diff-inline.ts";
|
|
23
|
+
import { resolveDiffPalette, type DiffTheme } from "./diff-palette.ts";
|
|
24
|
+
import {
|
|
25
|
+
createCodeLineHighlighter,
|
|
26
|
+
resolveLanguageFromPath,
|
|
27
|
+
type CodeLineHighlighter,
|
|
28
|
+
} from "./diff-highlight.ts";
|
|
29
|
+
import {
|
|
30
|
+
canRenderSplitLayout,
|
|
31
|
+
renderCompact,
|
|
32
|
+
renderSplit,
|
|
33
|
+
renderUnified,
|
|
34
|
+
type DiffRenderContext,
|
|
35
|
+
type RenderedRow,
|
|
36
|
+
} from "./diff-layout.ts";
|
|
37
|
+
import {
|
|
38
|
+
renderDiffFrameLine,
|
|
39
|
+
renderSingleDiffRow,
|
|
40
|
+
renderWriteHeader,
|
|
41
|
+
} from "./diff-header.ts";
|
|
42
|
+
import {
|
|
43
|
+
applyLineLimit,
|
|
44
|
+
clampDiffLineToWidth,
|
|
45
|
+
clampDiffLinesToWidth,
|
|
46
|
+
resolveDiffDisplayLimit,
|
|
47
|
+
resolveDiffProcessBudget,
|
|
48
|
+
resolveWriteCollapsedLimit,
|
|
49
|
+
takeEntriesForLineBudget,
|
|
50
|
+
takeSplitRowsForBudget,
|
|
51
|
+
} from "./diff-limits.ts";
|
|
52
|
+
import {
|
|
53
|
+
RICH_DIFF_COMPONENT,
|
|
54
|
+
createDiffRenderCache,
|
|
55
|
+
displayConfigCacheKey,
|
|
56
|
+
resolveDiffIndicatorMode,
|
|
57
|
+
resolveLiveDisplayConfig,
|
|
58
|
+
type DiffRenderOptions,
|
|
59
|
+
type DisplayConfigInput,
|
|
60
|
+
} from "./diff-component.ts";
|
|
61
|
+
import {
|
|
62
|
+
buildDiffSummaryText,
|
|
63
|
+
normalizeDiffRenderWidth,
|
|
64
|
+
resolveDiffPresentationMode,
|
|
65
|
+
type DiffPresentationMode,
|
|
66
|
+
} from "./diff-presentation.ts";
|
|
67
|
+
|
|
68
|
+
type WriteDiffOperationKind = "context" | "remove" | "add";
|
|
69
|
+
|
|
70
|
+
interface WriteDiffOperation {
|
|
71
|
+
kind: WriteDiffOperationKind;
|
|
72
|
+
content: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function buildWriteDiffOperations(
|
|
76
|
+
oldLines: string[],
|
|
77
|
+
newLines: string[],
|
|
78
|
+
): WriteDiffOperation[] {
|
|
79
|
+
const oldLength = oldLines.length;
|
|
80
|
+
const newLength = newLines.length;
|
|
81
|
+
const table: number[][] = Array.from({ length: oldLength + 1 }, () =>
|
|
82
|
+
Array<number>(newLength + 1).fill(0),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
for (let oldIndex = 1; oldIndex <= oldLength; oldIndex++) {
|
|
86
|
+
for (let newIndex = 1; newIndex <= newLength; newIndex++) {
|
|
87
|
+
if ((oldLines[oldIndex - 1] ?? "") === (newLines[newIndex - 1] ?? "")) {
|
|
88
|
+
table[oldIndex]![newIndex] =
|
|
89
|
+
(table[oldIndex - 1]?.[newIndex - 1] ?? 0) + 1;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const top = table[oldIndex - 1]?.[newIndex] ?? 0;
|
|
93
|
+
const left = table[oldIndex]?.[newIndex - 1] ?? 0;
|
|
94
|
+
table[oldIndex]![newIndex] = Math.max(top, left);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const operations: WriteDiffOperation[] = [];
|
|
99
|
+
let oldCursor = oldLength;
|
|
100
|
+
let newCursor = newLength;
|
|
101
|
+
|
|
102
|
+
while (oldCursor > 0 || newCursor > 0) {
|
|
103
|
+
const oldLine = oldCursor > 0 ? (oldLines[oldCursor - 1] ?? "") : undefined;
|
|
104
|
+
const newLine = newCursor > 0 ? (newLines[newCursor - 1] ?? "") : undefined;
|
|
105
|
+
|
|
106
|
+
if (oldCursor > 0 && newCursor > 0 && oldLine === newLine) {
|
|
107
|
+
operations.push({ kind: "context", content: oldLine ?? "" });
|
|
108
|
+
oldCursor--;
|
|
109
|
+
newCursor--;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const top = oldCursor > 0 ? (table[oldCursor - 1]?.[newCursor] ?? 0) : -1;
|
|
114
|
+
const left = newCursor > 0 ? (table[oldCursor]?.[newCursor - 1] ?? 0) : -1;
|
|
115
|
+
|
|
116
|
+
if (newCursor > 0 && left >= top) {
|
|
117
|
+
operations.push({ kind: "add", content: newLine ?? "" });
|
|
118
|
+
newCursor--;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (oldCursor > 0) {
|
|
123
|
+
operations.push({ kind: "remove", content: oldLine ?? "" });
|
|
124
|
+
oldCursor--;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
operations.reverse();
|
|
129
|
+
return operations;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function buildWriteEntries(lines: string[]): ParsedDiffEntry[] {
|
|
133
|
+
return lines.map((line, index) => ({
|
|
134
|
+
kind: "line",
|
|
135
|
+
lineKind: "add",
|
|
136
|
+
oldLineNumber: null,
|
|
137
|
+
newLineNumber: index + 1,
|
|
138
|
+
fallbackLineNumber: `${index + 1}`,
|
|
139
|
+
content: line,
|
|
140
|
+
raw: `+${line}`,
|
|
141
|
+
hunkIndex: 1,
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function buildWriteOverwriteEntries(
|
|
146
|
+
oldLines: string[],
|
|
147
|
+
newLines: string[],
|
|
148
|
+
): ParsedDiffEntry[] {
|
|
149
|
+
const operations = buildWriteDiffOperations(oldLines, newLines);
|
|
150
|
+
const entries: ParsedDiffEntry[] = [];
|
|
151
|
+
let oldLineNumber = 1;
|
|
152
|
+
let newLineNumber = 1;
|
|
153
|
+
|
|
154
|
+
for (const operation of operations) {
|
|
155
|
+
if (operation.kind === "context") {
|
|
156
|
+
entries.push({
|
|
157
|
+
kind: "line",
|
|
158
|
+
lineKind: "context",
|
|
159
|
+
oldLineNumber,
|
|
160
|
+
newLineNumber,
|
|
161
|
+
fallbackLineNumber: `${newLineNumber}`,
|
|
162
|
+
content: operation.content,
|
|
163
|
+
raw: ` ${operation.content}`,
|
|
164
|
+
hunkIndex: 1,
|
|
165
|
+
});
|
|
166
|
+
oldLineNumber++;
|
|
167
|
+
newLineNumber++;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (operation.kind === "remove") {
|
|
172
|
+
entries.push({
|
|
173
|
+
kind: "line",
|
|
174
|
+
lineKind: "remove",
|
|
175
|
+
oldLineNumber,
|
|
176
|
+
newLineNumber: null,
|
|
177
|
+
fallbackLineNumber: `${oldLineNumber}`,
|
|
178
|
+
content: operation.content,
|
|
179
|
+
raw: `-${operation.content}`,
|
|
180
|
+
hunkIndex: 1,
|
|
181
|
+
});
|
|
182
|
+
oldLineNumber++;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
entries.push({
|
|
187
|
+
kind: "line",
|
|
188
|
+
lineKind: "add",
|
|
189
|
+
oldLineNumber: null,
|
|
190
|
+
newLineNumber,
|
|
191
|
+
fallbackLineNumber: `${newLineNumber}`,
|
|
192
|
+
content: operation.content,
|
|
193
|
+
raw: `+${operation.content}`,
|
|
194
|
+
hunkIndex: 1,
|
|
195
|
+
});
|
|
196
|
+
newLineNumber++;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return entries;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
interface WriteDiffData {
|
|
203
|
+
entries: ParsedDiffEntry[];
|
|
204
|
+
splitRows: SplitDiffRow[];
|
|
205
|
+
inlineHighlights: WeakMap<DiffLineEntry, DiffSpan[]>;
|
|
206
|
+
lineNumberWidth: number;
|
|
207
|
+
stats: DiffStats;
|
|
208
|
+
hunkCount: number;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
interface WriteOverwriteGuard {
|
|
212
|
+
previousLineCount: number;
|
|
213
|
+
nextLineCount: number;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const MAX_WRITE_OVERWRITE_DIFF_LINES = 4000;
|
|
217
|
+
const MAX_WRITE_OVERWRITE_DIFF_MATRIX_CELLS = 1_000_000;
|
|
218
|
+
/** Collapsed overwrite previews refuse larger LCS matrices to avoid TUI freezes. */
|
|
219
|
+
const MAX_COLLAPSED_WRITE_OVERWRITE_DIFF_MATRIX_CELLS = 200_000;
|
|
220
|
+
|
|
221
|
+
function buildApproximateWriteStats(
|
|
222
|
+
lineCount: number,
|
|
223
|
+
previousLineCount: number,
|
|
224
|
+
hasComparablePrevious: boolean,
|
|
225
|
+
): DiffStats {
|
|
226
|
+
const removed = hasComparablePrevious ? previousLineCount : 0;
|
|
227
|
+
const added = lineCount;
|
|
228
|
+
const hasContent = lineCount > 0 || removed > 0;
|
|
229
|
+
return {
|
|
230
|
+
added,
|
|
231
|
+
removed,
|
|
232
|
+
context: 0,
|
|
233
|
+
hunks: hasContent ? 1 : 0,
|
|
234
|
+
files: 1,
|
|
235
|
+
lines: added + removed,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function buildWriteDiffData(entries: ParsedDiffEntry[]): WriteDiffData {
|
|
240
|
+
const splitRows = buildSplitRows(entries);
|
|
241
|
+
const inlineHighlights = buildInlineHighlightMap(splitRows);
|
|
242
|
+
const lineNumberWidth = getLineNumberWidth(entries);
|
|
243
|
+
const hunkCount = entries.length > 0 ? 1 : 0;
|
|
244
|
+
const stats = collectDiffStats(entries, hunkCount, 1);
|
|
245
|
+
return {
|
|
246
|
+
entries,
|
|
247
|
+
splitRows,
|
|
248
|
+
inlineHighlights,
|
|
249
|
+
lineNumberWidth,
|
|
250
|
+
stats,
|
|
251
|
+
hunkCount,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function resolveWriteOverwriteGuard(
|
|
256
|
+
previousLines: string[],
|
|
257
|
+
nextLines: string[],
|
|
258
|
+
expanded = true,
|
|
259
|
+
): WriteOverwriteGuard | undefined {
|
|
260
|
+
const previousLineCount = previousLines.length;
|
|
261
|
+
const nextLineCount = nextLines.length;
|
|
262
|
+
if (
|
|
263
|
+
previousLineCount > MAX_WRITE_OVERWRITE_DIFF_LINES ||
|
|
264
|
+
nextLineCount > MAX_WRITE_OVERWRITE_DIFF_LINES
|
|
265
|
+
) {
|
|
266
|
+
return { previousLineCount, nextLineCount };
|
|
267
|
+
}
|
|
268
|
+
if (previousLineCount === 0 || nextLineCount === 0) {
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
const cellLimit = expanded
|
|
272
|
+
? MAX_WRITE_OVERWRITE_DIFF_MATRIX_CELLS
|
|
273
|
+
: MAX_COLLAPSED_WRITE_OVERWRITE_DIFF_MATRIX_CELLS;
|
|
274
|
+
return previousLineCount * nextLineCount > cellLimit
|
|
275
|
+
? { previousLineCount, nextLineCount }
|
|
276
|
+
: undefined;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function buildWriteOverwriteGuardText(
|
|
280
|
+
guard: WriteOverwriteGuard,
|
|
281
|
+
width: number,
|
|
282
|
+
): string {
|
|
283
|
+
const safeWidth = normalizeDiffRenderWidth(width);
|
|
284
|
+
if (safeWidth === 0) {
|
|
285
|
+
return "";
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const candidates = [
|
|
289
|
+
`↳ overwrite diff omitted (${guard.previousLineCount} → ${guard.nextLineCount} lines)`,
|
|
290
|
+
`↳ overwrite diff omitted (${guard.previousLineCount}→${guard.nextLineCount})`,
|
|
291
|
+
"↳ overwrite diff omitted",
|
|
292
|
+
"diff omitted",
|
|
293
|
+
"…",
|
|
294
|
+
];
|
|
295
|
+
for (const candidate of candidates) {
|
|
296
|
+
if (visibleWidth(candidate) <= safeWidth) {
|
|
297
|
+
return candidate;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return truncateToWidth(
|
|
301
|
+
candidates[candidates.length - 1] ?? "",
|
|
302
|
+
safeWidth,
|
|
303
|
+
"",
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function renderWriteOverwriteGuardRows(
|
|
308
|
+
guard: WriteOverwriteGuard,
|
|
309
|
+
width: number,
|
|
310
|
+
theme: DiffTheme,
|
|
311
|
+
): string[] {
|
|
312
|
+
return renderSingleDiffRow(
|
|
313
|
+
buildWriteOverwriteGuardText(guard, width),
|
|
314
|
+
"warning",
|
|
315
|
+
width,
|
|
316
|
+
theme,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** Folded write with `writeDiffCollapsedLines: 0`: `↳ created • click to show more`. */
|
|
321
|
+
function renderWriteCollapsedHintLine(
|
|
322
|
+
wasOverwrite: boolean,
|
|
323
|
+
width: number,
|
|
324
|
+
theme: DiffTheme,
|
|
325
|
+
hovered: boolean,
|
|
326
|
+
headerLabel?: string,
|
|
327
|
+
): string[] {
|
|
328
|
+
const safeWidth = normalizeDiffRenderWidth(width);
|
|
329
|
+
if (safeWidth === 0) {
|
|
330
|
+
return [""];
|
|
331
|
+
}
|
|
332
|
+
const actionLabel =
|
|
333
|
+
headerLabel?.trim() || (wasOverwrite ? "overwritten" : "created");
|
|
334
|
+
const clickLabel = showMoreHintText();
|
|
335
|
+
const candidates = [
|
|
336
|
+
`↳ ${actionLabel} • ${clickLabel}`,
|
|
337
|
+
`↳ ${actionLabel}`,
|
|
338
|
+
actionLabel,
|
|
339
|
+
"…",
|
|
340
|
+
];
|
|
341
|
+
let text = candidates[candidates.length - 1] ?? "…";
|
|
342
|
+
for (const candidate of candidates) {
|
|
343
|
+
if (visibleWidth(candidate) <= safeWidth) {
|
|
344
|
+
text = candidate;
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (visibleWidth(text) > safeWidth) {
|
|
349
|
+
text = truncateToWidth(text, safeWidth, "");
|
|
350
|
+
}
|
|
351
|
+
const clickIndex = text.lastIndexOf(clickLabel);
|
|
352
|
+
const styled =
|
|
353
|
+
hovered && clickIndex >= 0
|
|
354
|
+
? theme.fg("muted", text.slice(0, clickIndex)) +
|
|
355
|
+
theme.fg("text", clickLabel) +
|
|
356
|
+
theme.fg("muted", text.slice(clickIndex + clickLabel.length))
|
|
357
|
+
: theme.fg("muted", text);
|
|
358
|
+
return [clampDiffLineToWidth(styled, safeWidth)];
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* write 变更行统计(compact 单行用):复用当前 write diff 算法。
|
|
363
|
+
* 新文件 = 全部新内容为 added;覆盖已有文件用 LCS 精确统计。
|
|
364
|
+
* content 非字符串、元数据缺失或超过矩阵预算时返回 undefined(不显示计数)。
|
|
365
|
+
*/
|
|
366
|
+
export function countWriteDiffStats(
|
|
367
|
+
content: string | undefined,
|
|
368
|
+
previousContent: string | undefined,
|
|
369
|
+
fileExistedBeforeWrite: boolean | undefined,
|
|
370
|
+
): { added: number; removed: number } | undefined {
|
|
371
|
+
if (typeof content !== "string" || fileExistedBeforeWrite === undefined)
|
|
372
|
+
return undefined;
|
|
373
|
+
const lines = splitWriteContentLines(sanitizeToolResultText(content));
|
|
374
|
+
if (fileExistedBeforeWrite === false)
|
|
375
|
+
return { added: lines.length, removed: 0 };
|
|
376
|
+
if (typeof previousContent !== "string") return undefined;
|
|
377
|
+
const previousLines = splitWriteContentLines(
|
|
378
|
+
sanitizeToolResultText(previousContent),
|
|
379
|
+
);
|
|
380
|
+
if (previousLines.length === 0 || lines.length === 0) {
|
|
381
|
+
return { added: lines.length, removed: previousLines.length };
|
|
382
|
+
}
|
|
383
|
+
if (
|
|
384
|
+
previousLines.length * lines.length >
|
|
385
|
+
MAX_COLLAPSED_WRITE_OVERWRITE_DIFF_MATRIX_CELLS
|
|
386
|
+
) {
|
|
387
|
+
return undefined;
|
|
388
|
+
}
|
|
389
|
+
let added = 0;
|
|
390
|
+
let removed = 0;
|
|
391
|
+
for (const operation of buildWriteDiffOperations(previousLines, lines)) {
|
|
392
|
+
if (operation.kind === "add") added++;
|
|
393
|
+
else if (operation.kind === "remove") removed++;
|
|
394
|
+
}
|
|
395
|
+
return { added, removed };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function renderWriteDiffResult(
|
|
399
|
+
content: string | undefined,
|
|
400
|
+
options: DiffRenderOptions,
|
|
401
|
+
config: DisplayConfigInput,
|
|
402
|
+
theme: DiffTheme,
|
|
403
|
+
fallbackText: string,
|
|
404
|
+
): Component {
|
|
405
|
+
const safeFallbackText = sanitizeToolResultText(fallbackText);
|
|
406
|
+
if (typeof content !== "string") {
|
|
407
|
+
if (!safeFallbackText.trim()) {
|
|
408
|
+
return new Text(theme.fg("muted", "↳ write completed"), 0, 0);
|
|
409
|
+
}
|
|
410
|
+
return new Text(theme.fg("toolOutput", safeFallbackText), 0, 0);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const filePath = options.filePath?.trim() || "(unknown path)";
|
|
414
|
+
const lines = splitWriteContentLines(sanitizeToolResultText(content));
|
|
415
|
+
const previousLines =
|
|
416
|
+
typeof options.previousContent === "string"
|
|
417
|
+
? splitWriteContentLines(sanitizeToolResultText(options.previousContent))
|
|
418
|
+
: [];
|
|
419
|
+
const hasComparablePrevious =
|
|
420
|
+
options.fileExistedBeforeWrite === true &&
|
|
421
|
+
typeof options.previousContent === "string";
|
|
422
|
+
const approximateStats = buildApproximateWriteStats(
|
|
423
|
+
lines.length,
|
|
424
|
+
previousLines.length,
|
|
425
|
+
hasComparablePrevious,
|
|
426
|
+
);
|
|
427
|
+
const palette = resolveDiffPalette(theme);
|
|
428
|
+
// Keep the panel transparent; changed rows and inline spans retain their own highlights.
|
|
429
|
+
const containerBgAnsi = undefined;
|
|
430
|
+
const language = resolveLanguageFromPath(filePath);
|
|
431
|
+
const cache = createDiffRenderCache();
|
|
432
|
+
let highlightLine: CodeLineHighlighter | undefined;
|
|
433
|
+
let detailedData: WriteDiffData | undefined;
|
|
434
|
+
|
|
435
|
+
function getDetailedData(): WriteDiffData {
|
|
436
|
+
if (detailedData) {
|
|
437
|
+
return detailedData;
|
|
438
|
+
}
|
|
439
|
+
const entries = hasComparablePrevious
|
|
440
|
+
? buildWriteOverwriteEntries(previousLines, lines)
|
|
441
|
+
: buildWriteEntries(lines);
|
|
442
|
+
detailedData = buildWriteDiffData(entries);
|
|
443
|
+
highlightLine = createCodeLineHighlighter(language, theme, entries, () => {
|
|
444
|
+
cache.invalidate();
|
|
445
|
+
options.invalidate?.();
|
|
446
|
+
});
|
|
447
|
+
return detailedData;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return {
|
|
451
|
+
[RICH_DIFF_COMPONENT]: true,
|
|
452
|
+
render(width: number): string[] {
|
|
453
|
+
// Live config: panel can change indicator/wrap/limits after this component is created.
|
|
454
|
+
const live = resolveLiveDisplayConfig(config);
|
|
455
|
+
const wordWrap = live.diffWordWrap;
|
|
456
|
+
const indicatorMode = resolveDiffIndicatorMode(live);
|
|
457
|
+
const configKey = displayConfigCacheKey(live);
|
|
458
|
+
const safeWidth = normalizeDiffRenderWidth(width);
|
|
459
|
+
const resolvedMode = resolveDiffPresentationMode(
|
|
460
|
+
live,
|
|
461
|
+
safeWidth,
|
|
462
|
+
canRenderSplitLayout(safeWidth),
|
|
463
|
+
);
|
|
464
|
+
const mode: DiffPresentationMode = hasComparablePrevious
|
|
465
|
+
? resolvedMode
|
|
466
|
+
: resolvedMode === "split"
|
|
467
|
+
? "unified"
|
|
468
|
+
: resolvedMode;
|
|
469
|
+
const hovered = options.isHovered?.() ?? false;
|
|
470
|
+
const cached = cache.get(
|
|
471
|
+
safeWidth,
|
|
472
|
+
options.expanded,
|
|
473
|
+
mode,
|
|
474
|
+
configKey,
|
|
475
|
+
hovered,
|
|
476
|
+
);
|
|
477
|
+
if (cached) {
|
|
478
|
+
return cached;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const writeCollapsedLimit = resolveWriteCollapsedLimit(live);
|
|
482
|
+
const statsOnlyCollapsed = !options.expanded && writeCollapsedLimit === 0;
|
|
483
|
+
if (statsOnlyCollapsed) {
|
|
484
|
+
return cache.set(
|
|
485
|
+
safeWidth,
|
|
486
|
+
options.expanded,
|
|
487
|
+
mode,
|
|
488
|
+
configKey,
|
|
489
|
+
hovered,
|
|
490
|
+
clampDiffLinesToWidth(
|
|
491
|
+
renderWriteCollapsedHintLine(
|
|
492
|
+
options.fileExistedBeforeWrite === true,
|
|
493
|
+
safeWidth,
|
|
494
|
+
theme,
|
|
495
|
+
hovered,
|
|
496
|
+
options.headerLabel,
|
|
497
|
+
),
|
|
498
|
+
safeWidth,
|
|
499
|
+
),
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const header = renderWriteHeader(
|
|
504
|
+
options.fileExistedBeforeWrite === true,
|
|
505
|
+
safeWidth,
|
|
506
|
+
theme,
|
|
507
|
+
options.headerLabel,
|
|
508
|
+
);
|
|
509
|
+
// Guard depends on expanded: collapsed previews use a stricter LCS cell budget.
|
|
510
|
+
const overwriteGuard = hasComparablePrevious
|
|
511
|
+
? resolveWriteOverwriteGuard(previousLines, lines, options.expanded)
|
|
512
|
+
: undefined;
|
|
513
|
+
if (overwriteGuard) {
|
|
514
|
+
return cache.set(
|
|
515
|
+
safeWidth,
|
|
516
|
+
options.expanded,
|
|
517
|
+
mode,
|
|
518
|
+
configKey,
|
|
519
|
+
hovered,
|
|
520
|
+
clampDiffLinesToWidth(
|
|
521
|
+
[
|
|
522
|
+
header,
|
|
523
|
+
...renderWriteOverwriteGuardRows(
|
|
524
|
+
overwriteGuard,
|
|
525
|
+
safeWidth,
|
|
526
|
+
theme,
|
|
527
|
+
),
|
|
528
|
+
],
|
|
529
|
+
safeWidth,
|
|
530
|
+
),
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (mode === "summary") {
|
|
535
|
+
const summaryRows =
|
|
536
|
+
approximateStats.lines === 0
|
|
537
|
+
? [header]
|
|
538
|
+
: [
|
|
539
|
+
header,
|
|
540
|
+
...renderSingleDiffRow(
|
|
541
|
+
buildDiffSummaryText(approximateStats, safeWidth),
|
|
542
|
+
"toolOutput",
|
|
543
|
+
safeWidth,
|
|
544
|
+
theme,
|
|
545
|
+
),
|
|
546
|
+
];
|
|
547
|
+
return cache.set(
|
|
548
|
+
safeWidth,
|
|
549
|
+
options.expanded,
|
|
550
|
+
mode,
|
|
551
|
+
configKey,
|
|
552
|
+
hovered,
|
|
553
|
+
clampDiffLinesToWidth(summaryRows, safeWidth),
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const data = getDetailedData();
|
|
558
|
+
const displayLimit = resolveDiffDisplayLimit(
|
|
559
|
+
options.expanded,
|
|
560
|
+
writeCollapsedLimit,
|
|
561
|
+
live.expandedPreviewMaxLines,
|
|
562
|
+
);
|
|
563
|
+
const processBudget = resolveDiffProcessBudget(displayLimit, wordWrap);
|
|
564
|
+
const entryBudget = takeEntriesForLineBudget(data.entries, processBudget);
|
|
565
|
+
const splitBudget = takeSplitRowsForBudget(data.splitRows, processBudget);
|
|
566
|
+
const inlineHighlights =
|
|
567
|
+
splitBudget.processedRows < splitBudget.totalRows
|
|
568
|
+
? buildInlineHighlightMap(splitBudget.rows)
|
|
569
|
+
: data.inlineHighlights;
|
|
570
|
+
const renderCtx: DiffRenderContext = {
|
|
571
|
+
width: safeWidth,
|
|
572
|
+
theme,
|
|
573
|
+
inlineHighlights,
|
|
574
|
+
palette,
|
|
575
|
+
highlightLine: highlightLine!,
|
|
576
|
+
containerBgAnsi,
|
|
577
|
+
wordWrap,
|
|
578
|
+
indicatorMode,
|
|
579
|
+
showHashlineAnchors: false,
|
|
580
|
+
};
|
|
581
|
+
const bodyRows: RenderedRow[] =
|
|
582
|
+
entryBudget.entries.length === 0
|
|
583
|
+
? [{ text: theme.fg("muted", "(empty file)"), hunkIndex: null }]
|
|
584
|
+
: mode === "split"
|
|
585
|
+
? renderSplit(splitBudget.rows, renderCtx, data.lineNumberWidth)
|
|
586
|
+
: mode === "compact"
|
|
587
|
+
? renderCompact(entryBudget.entries, renderCtx)
|
|
588
|
+
: renderUnified(
|
|
589
|
+
entryBudget.entries,
|
|
590
|
+
renderCtx,
|
|
591
|
+
data.lineNumberWidth,
|
|
592
|
+
);
|
|
593
|
+
const unprocessedLogicalRows =
|
|
594
|
+
mode === "split"
|
|
595
|
+
? Math.max(0, splitBudget.totalRows - splitBudget.processedRows)
|
|
596
|
+
: Math.max(
|
|
597
|
+
0,
|
|
598
|
+
entryBudget.totalLineEntries - entryBudget.processedLineEntries,
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
const bodyWithLimit = applyLineLimit(
|
|
602
|
+
bodyRows,
|
|
603
|
+
safeWidth,
|
|
604
|
+
options.expanded,
|
|
605
|
+
writeCollapsedLimit,
|
|
606
|
+
live.expandedPreviewMaxLines,
|
|
607
|
+
data.hunkCount,
|
|
608
|
+
theme,
|
|
609
|
+
unprocessedLogicalRows,
|
|
610
|
+
hovered,
|
|
611
|
+
);
|
|
612
|
+
const frame = renderDiffFrameLine(safeWidth, theme);
|
|
613
|
+
const renderedLines =
|
|
614
|
+
mode === "unified"
|
|
615
|
+
? [header, frame, ...bodyWithLimit, frame]
|
|
616
|
+
: [header, ...bodyWithLimit];
|
|
617
|
+
const finalLines = clampDiffLinesToWidth(renderedLines, safeWidth);
|
|
618
|
+
return cache.set(
|
|
619
|
+
safeWidth,
|
|
620
|
+
options.expanded,
|
|
621
|
+
mode,
|
|
622
|
+
configKey,
|
|
623
|
+
hovered,
|
|
624
|
+
finalLines,
|
|
625
|
+
);
|
|
626
|
+
},
|
|
627
|
+
invalidate: cache.invalidate,
|
|
628
|
+
} as Component;
|
|
629
|
+
}
|