hunkdiff 0.12.0-beta.0 → 0.12.0-beta.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/dist/npm/highlights-eq9cgrbb.scm +604 -0
- package/dist/npm/highlights-ghv9g403.scm +205 -0
- package/dist/npm/highlights-hk7bwhj4.scm +284 -0
- package/dist/npm/highlights-r812a2qc.scm +150 -0
- package/dist/npm/highlights-x6tmsnaa.scm +115 -0
- package/dist/npm/injections-73j83es3.scm +27 -0
- package/dist/npm/main.js +92455 -0
- package/dist/npm/opentui/HunkDiffBody.d.ts +3 -0
- package/dist/npm/opentui/HunkDiffFileHeader.d.ts +3 -0
- package/dist/npm/opentui/HunkDiffView.d.ts +3 -0
- package/dist/npm/opentui/HunkFileNav.d.ts +3 -0
- package/dist/npm/opentui/HunkReviewStream.d.ts +3 -0
- package/dist/npm/opentui/index.d.ts +9 -0
- package/dist/npm/opentui/index.js +2114 -0
- package/dist/npm/opentui/model.d.ts +16 -0
- package/dist/npm/opentui/themes.d.ts +2 -0
- package/dist/npm/opentui/types.d.ts +76 -0
- package/dist/npm/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
- package/dist/npm/tree-sitter-markdown-411r6y9b.wasm +0 -0
- package/dist/npm/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
- package/dist/npm/tree-sitter-typescript-zxjzwt75.wasm +0 -0
- package/dist/npm/tree-sitter-zig-e78zbjpm.wasm +0 -0
- package/package.json +26 -5
|
@@ -0,0 +1,2114 @@
|
|
|
1
|
+
// src/opentui/index.ts
|
|
2
|
+
import { parseDiffFromFile, parsePatchFiles as parsePatchFiles2 } from "@pierre/diffs";
|
|
3
|
+
|
|
4
|
+
// src/opentui/themes.ts
|
|
5
|
+
var HUNK_DIFF_THEME_NAMES = ["graphite", "midnight", "paper", "ember"];
|
|
6
|
+
// src/opentui/HunkDiffBody.tsx
|
|
7
|
+
import { useMemo } from "react";
|
|
8
|
+
|
|
9
|
+
// src/ui/diff/codeColumns.ts
|
|
10
|
+
var DIFF_CODE_TAB_WIDTH = 2;
|
|
11
|
+
var DIFF_RAIL_PREFIX_WIDTH = 1;
|
|
12
|
+
var DIFF_SPLIT_SEPARATOR_WIDTH = 1;
|
|
13
|
+
function expandDiffTabs(text) {
|
|
14
|
+
return text.replaceAll("\t", " ".repeat(DIFF_CODE_TAB_WIDTH));
|
|
15
|
+
}
|
|
16
|
+
function findMaxLineNumber(file) {
|
|
17
|
+
let highest = 0;
|
|
18
|
+
for (const hunk of file.metadata.hunks) {
|
|
19
|
+
highest = Math.max(highest, hunk.deletionStart + hunk.deletionCount, hunk.additionStart + hunk.additionCount);
|
|
20
|
+
}
|
|
21
|
+
return Math.max(highest, 1);
|
|
22
|
+
}
|
|
23
|
+
function resolveSplitPaneWidths(width) {
|
|
24
|
+
const usableWidth = Math.max(0, width - DIFF_RAIL_PREFIX_WIDTH - DIFF_SPLIT_SEPARATOR_WIDTH);
|
|
25
|
+
const leftWidth = Math.max(0, DIFF_RAIL_PREFIX_WIDTH + Math.floor(usableWidth / 2));
|
|
26
|
+
const rightWidth = Math.max(0, DIFF_SPLIT_SEPARATOR_WIDTH + usableWidth - Math.floor(usableWidth / 2));
|
|
27
|
+
return { leftWidth, rightWidth };
|
|
28
|
+
}
|
|
29
|
+
function resolveSplitCellGeometry(width, lineNumberDigits, showLineNumbers, prefixWidth = DIFF_RAIL_PREFIX_WIDTH) {
|
|
30
|
+
const availableWidth = Math.max(0, width - prefixWidth);
|
|
31
|
+
const gutterWidth = Math.min(availableWidth, showLineNumbers ? lineNumberDigits + 3 : 2);
|
|
32
|
+
return {
|
|
33
|
+
gutterWidth,
|
|
34
|
+
contentWidth: Math.max(0, availableWidth - gutterWidth)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function resolveStackCellGeometry(width, lineNumberDigits, showLineNumbers, prefixWidth = DIFF_RAIL_PREFIX_WIDTH) {
|
|
38
|
+
const availableWidth = Math.max(0, width - prefixWidth);
|
|
39
|
+
const gutterWidth = Math.min(availableWidth, showLineNumbers ? lineNumberDigits * 2 + 5 : 2);
|
|
40
|
+
return {
|
|
41
|
+
gutterWidth,
|
|
42
|
+
contentWidth: Math.max(0, availableWidth - gutterWidth)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/ui/diff/pierre.ts
|
|
47
|
+
import {
|
|
48
|
+
cleanLastNewline,
|
|
49
|
+
getHighlighterOptions,
|
|
50
|
+
getSharedHighlighter,
|
|
51
|
+
renderDiffWithHighlighter
|
|
52
|
+
} from "@pierre/diffs";
|
|
53
|
+
|
|
54
|
+
// src/core/hunkHeader.ts
|
|
55
|
+
function formatHunkHeader(hunk) {
|
|
56
|
+
const specs = hunk.hunkSpecs ?? `@@ -${hunk.deletionStart},${hunk.deletionLines} +${hunk.additionStart},${hunk.additionLines} @@`;
|
|
57
|
+
return hunk.hunkContext ? `${specs} ${hunk.hunkContext}` : specs;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/ui/lib/color.ts
|
|
61
|
+
function hexToRgb(hex) {
|
|
62
|
+
const normalized = /^#?[0-9a-f]{6}$/i.test(hex) ? hex.replace(/^#/, "") : "000000";
|
|
63
|
+
const value = parseInt(normalized, 16);
|
|
64
|
+
return {
|
|
65
|
+
r: value >> 16 & 255,
|
|
66
|
+
g: value >> 8 & 255,
|
|
67
|
+
b: value & 255
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function blendHex(fg, bg, ratio) {
|
|
71
|
+
const foreground = hexToRgb(fg);
|
|
72
|
+
const background = hexToRgb(bg);
|
|
73
|
+
const mix = (front, back) => Math.max(0, Math.min(255, Math.round(back + (front - back) * ratio)));
|
|
74
|
+
return `#${(mix(foreground.r, background.r) << 16 | mix(foreground.g, background.g) << 8 | mix(foreground.b, background.b)).toString(16).padStart(6, "0")}`;
|
|
75
|
+
}
|
|
76
|
+
function hexColorDistance(left, right) {
|
|
77
|
+
const a = hexToRgb(left);
|
|
78
|
+
const b = hexToRgb(right);
|
|
79
|
+
return Math.abs(a.r - b.r) + Math.abs(a.g - b.g) + Math.abs(a.b - b.b);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/ui/diff/pierre.ts
|
|
83
|
+
var PIERRE_THEME = {
|
|
84
|
+
light: "pierre-light",
|
|
85
|
+
dark: "pierre-dark"
|
|
86
|
+
};
|
|
87
|
+
function pierreThemeName(appearance) {
|
|
88
|
+
return PIERRE_THEME[appearance];
|
|
89
|
+
}
|
|
90
|
+
var PIERRE_RENDER_OPTIONS_BY_APPEARANCE = {
|
|
91
|
+
light: {
|
|
92
|
+
theme: pierreThemeName("light"),
|
|
93
|
+
useTokenTransformer: false,
|
|
94
|
+
tokenizeMaxLineLength: 1000,
|
|
95
|
+
lineDiffType: "word-alt",
|
|
96
|
+
maxLineDiffLength: 1e4
|
|
97
|
+
},
|
|
98
|
+
dark: {
|
|
99
|
+
theme: pierreThemeName("dark"),
|
|
100
|
+
useTokenTransformer: false,
|
|
101
|
+
tokenizeMaxLineLength: 1000,
|
|
102
|
+
lineDiffType: "word-alt",
|
|
103
|
+
maxLineDiffLength: 1e4
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
function pierreRenderOptions(appearance) {
|
|
107
|
+
return PIERRE_RENDER_OPTIONS_BY_APPEARANCE[appearance];
|
|
108
|
+
}
|
|
109
|
+
var highlighterOptionsByKey = new Map;
|
|
110
|
+
var queuedHighlightWork = Promise.resolve();
|
|
111
|
+
function tabify(text) {
|
|
112
|
+
return expandDiffTabs(text);
|
|
113
|
+
}
|
|
114
|
+
var EMPTY_STYLE_VALUES = new Map;
|
|
115
|
+
var parsedStyleValueCache = new Map;
|
|
116
|
+
function parseStyleValue(styleValue) {
|
|
117
|
+
if (typeof styleValue !== "string") {
|
|
118
|
+
return EMPTY_STYLE_VALUES;
|
|
119
|
+
}
|
|
120
|
+
const cached = parsedStyleValueCache.get(styleValue);
|
|
121
|
+
if (cached) {
|
|
122
|
+
return cached;
|
|
123
|
+
}
|
|
124
|
+
const styles = new Map;
|
|
125
|
+
for (const segment of styleValue.split(";")) {
|
|
126
|
+
const separator = segment.indexOf(":");
|
|
127
|
+
if (separator <= 0) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const key = segment.slice(0, separator).trim();
|
|
131
|
+
const value = segment.slice(separator + 1).trim();
|
|
132
|
+
if (key && value) {
|
|
133
|
+
styles.set(key, value);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
parsedStyleValueCache.set(styleValue, styles);
|
|
137
|
+
return styles;
|
|
138
|
+
}
|
|
139
|
+
var RESERVED_PIERRE_TOKEN_COLORS = {
|
|
140
|
+
dark: {
|
|
141
|
+
"#ff6762": "keyword",
|
|
142
|
+
"#5ecc71": "string"
|
|
143
|
+
},
|
|
144
|
+
light: {
|
|
145
|
+
"#d52c36": "keyword",
|
|
146
|
+
"#199f43": "string"
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
var normalizedColorCache = new Map;
|
|
150
|
+
var flattenedHighlightedLineCache = new WeakMap;
|
|
151
|
+
var MIN_WORD_DIFF_BG_DISTANCE = 28;
|
|
152
|
+
var WORD_DIFF_BLEND_STEP = 0.005;
|
|
153
|
+
var WORD_DIFF_MAX_BLEND = 0.2;
|
|
154
|
+
var wordDiffBackgroundCache = new Map;
|
|
155
|
+
function strengthenWordDiffBg(lineBg, signColor) {
|
|
156
|
+
let strongestCandidate = lineBg;
|
|
157
|
+
const maxSteps = Math.floor(WORD_DIFF_MAX_BLEND / WORD_DIFF_BLEND_STEP);
|
|
158
|
+
for (let step = 1;step <= maxSteps; step += 1) {
|
|
159
|
+
const blendRatio = step * WORD_DIFF_BLEND_STEP;
|
|
160
|
+
const candidate = blendHex(signColor, lineBg, blendRatio);
|
|
161
|
+
strongestCandidate = candidate;
|
|
162
|
+
if (hexColorDistance(candidate, lineBg) >= MIN_WORD_DIFF_BG_DISTANCE) {
|
|
163
|
+
return candidate;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return strongestCandidate;
|
|
167
|
+
}
|
|
168
|
+
function wordDiffHighlightBg(kind, theme) {
|
|
169
|
+
let cached = wordDiffBackgroundCache.get(theme.id);
|
|
170
|
+
if (!cached) {
|
|
171
|
+
const addition = hexColorDistance(theme.addedContentBg, theme.addedBg) >= MIN_WORD_DIFF_BG_DISTANCE ? theme.addedContentBg : strengthenWordDiffBg(theme.addedBg, theme.addedSignColor);
|
|
172
|
+
const deletion = hexColorDistance(theme.removedContentBg, theme.removedBg) >= MIN_WORD_DIFF_BG_DISTANCE ? theme.removedContentBg : strengthenWordDiffBg(theme.removedBg, theme.removedSignColor);
|
|
173
|
+
cached = {
|
|
174
|
+
addition,
|
|
175
|
+
context: theme.contextContentBg,
|
|
176
|
+
deletion,
|
|
177
|
+
empty: theme.panelAlt
|
|
178
|
+
};
|
|
179
|
+
wordDiffBackgroundCache.set(theme.id, cached);
|
|
180
|
+
}
|
|
181
|
+
return cached[kind];
|
|
182
|
+
}
|
|
183
|
+
function normalizeHighlightedColor(color, theme) {
|
|
184
|
+
if (!color) {
|
|
185
|
+
return color;
|
|
186
|
+
}
|
|
187
|
+
let cacheForTheme = normalizedColorCache.get(theme.id);
|
|
188
|
+
if (!cacheForTheme) {
|
|
189
|
+
cacheForTheme = new Map;
|
|
190
|
+
normalizedColorCache.set(theme.id, cacheForTheme);
|
|
191
|
+
}
|
|
192
|
+
const cached = cacheForTheme.get(color);
|
|
193
|
+
if (cached) {
|
|
194
|
+
return cached;
|
|
195
|
+
}
|
|
196
|
+
const normalized = color.trim().toLowerCase();
|
|
197
|
+
const reserved = RESERVED_PIERRE_TOKEN_COLORS[theme.appearance][normalized];
|
|
198
|
+
const resolvedColor = reserved ? theme.syntaxColors[reserved] : color;
|
|
199
|
+
cacheForTheme.set(color, resolvedColor);
|
|
200
|
+
return resolvedColor;
|
|
201
|
+
}
|
|
202
|
+
function mergeSpan(target, next) {
|
|
203
|
+
if (next.text.length === 0) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const previous = target[target.length - 1];
|
|
207
|
+
if (previous && previous.fg === next.fg && previous.bg === next.bg) {
|
|
208
|
+
previous.text += next.text;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
target.push(next);
|
|
212
|
+
}
|
|
213
|
+
function flattenHighlightedLine(node, theme, emphasisBg) {
|
|
214
|
+
if (!node) {
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
const cacheKey = `${theme.id}:${emphasisBg}`;
|
|
218
|
+
const cachedByTheme = flattenedHighlightedLineCache.get(node);
|
|
219
|
+
const cached = cachedByTheme?.get(cacheKey);
|
|
220
|
+
if (cached) {
|
|
221
|
+
return cached;
|
|
222
|
+
}
|
|
223
|
+
const spans = [];
|
|
224
|
+
const colorVariable = theme.appearance === "light" ? "--diffs-token-light" : "--diffs-token-dark";
|
|
225
|
+
const visit = (current, inherited) => {
|
|
226
|
+
if (!current) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (current.type === "text") {
|
|
230
|
+
mergeSpan(spans, {
|
|
231
|
+
text: tabify(cleanLastNewline(current.value)),
|
|
232
|
+
fg: inherited.fg,
|
|
233
|
+
bg: inherited.bg
|
|
234
|
+
});
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const properties = current.properties ?? {};
|
|
238
|
+
const styles = parseStyleValue(properties.style);
|
|
239
|
+
const nextStyle = {
|
|
240
|
+
fg: normalizeHighlightedColor(styles.get(colorVariable) ?? styles.get("color") ?? inherited.fg, theme),
|
|
241
|
+
bg: Object.hasOwn(properties, "data-diff-span") ? emphasisBg : inherited.bg
|
|
242
|
+
};
|
|
243
|
+
for (const child of current.children ?? []) {
|
|
244
|
+
visit(child, nextStyle);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
visit(node, {});
|
|
248
|
+
const nextCachedByTheme = cachedByTheme ?? new Map;
|
|
249
|
+
nextCachedByTheme.set(cacheKey, spans);
|
|
250
|
+
if (!cachedByTheme) {
|
|
251
|
+
flattenedHighlightedLineCache.set(node, nextCachedByTheme);
|
|
252
|
+
}
|
|
253
|
+
return spans;
|
|
254
|
+
}
|
|
255
|
+
function cleanDiffLine(line) {
|
|
256
|
+
return tabify(cleanLastNewline(line ?? ""));
|
|
257
|
+
}
|
|
258
|
+
function makeSplitCell(kind, lineNumber, rawLine, highlightedLine, theme) {
|
|
259
|
+
if (kind === "empty") {
|
|
260
|
+
return {
|
|
261
|
+
kind,
|
|
262
|
+
sign: " ",
|
|
263
|
+
spans: []
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
let spans;
|
|
267
|
+
if (highlightedLine === undefined) {
|
|
268
|
+
const fallbackText = cleanDiffLine(rawLine);
|
|
269
|
+
spans = fallbackText.length > 0 ? [{ text: fallbackText }] : [];
|
|
270
|
+
} else {
|
|
271
|
+
spans = flattenHighlightedLine(highlightedLine, theme, wordDiffHighlightBg(kind, theme));
|
|
272
|
+
if (spans.length === 0) {
|
|
273
|
+
const fallbackText = cleanDiffLine(rawLine);
|
|
274
|
+
spans = fallbackText.length > 0 ? [{ text: fallbackText }] : [];
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
kind,
|
|
279
|
+
sign: kind === "addition" ? "+" : kind === "deletion" ? "-" : " ",
|
|
280
|
+
lineNumber,
|
|
281
|
+
spans
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function makeStackCell(kind, oldLineNumber, newLineNumber, rawLine, highlightedLine, theme) {
|
|
285
|
+
let spans;
|
|
286
|
+
if (highlightedLine === undefined) {
|
|
287
|
+
const fallbackText = cleanDiffLine(rawLine);
|
|
288
|
+
spans = fallbackText.length > 0 ? [{ text: fallbackText }] : [];
|
|
289
|
+
} else {
|
|
290
|
+
spans = flattenHighlightedLine(highlightedLine, theme, wordDiffHighlightBg(kind, theme));
|
|
291
|
+
if (spans.length === 0) {
|
|
292
|
+
const fallbackText = cleanDiffLine(rawLine);
|
|
293
|
+
spans = fallbackText.length > 0 ? [{ text: fallbackText }] : [];
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
kind,
|
|
298
|
+
sign: kind === "addition" ? "+" : kind === "deletion" ? "-" : " ",
|
|
299
|
+
oldLineNumber,
|
|
300
|
+
newLineNumber,
|
|
301
|
+
spans
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function collapsedRowText(lines) {
|
|
305
|
+
return `${lines} unchanged ${lines === 1 ? "line" : "lines"}`;
|
|
306
|
+
}
|
|
307
|
+
function trailingCollapsedLines(metadata) {
|
|
308
|
+
const lastHunk = metadata.hunks.at(-1);
|
|
309
|
+
if (!lastHunk || metadata.isPartial) {
|
|
310
|
+
return 0;
|
|
311
|
+
}
|
|
312
|
+
const additionRemaining = metadata.additionLines.length - (lastHunk.additionLineIndex + lastHunk.additionCount);
|
|
313
|
+
const deletionRemaining = metadata.deletionLines.length - (lastHunk.deletionLineIndex + lastHunk.deletionCount);
|
|
314
|
+
if (additionRemaining !== deletionRemaining) {
|
|
315
|
+
return 0;
|
|
316
|
+
}
|
|
317
|
+
return Math.max(additionRemaining, 0);
|
|
318
|
+
}
|
|
319
|
+
async function prepareHighlighter(language, appearance) {
|
|
320
|
+
const resolvedLanguage = language ?? "text";
|
|
321
|
+
const cacheKey = `${appearance}:${resolvedLanguage}`;
|
|
322
|
+
const options = highlighterOptionsByKey.get(cacheKey) ?? getHighlighterOptions(resolvedLanguage, {
|
|
323
|
+
theme: pierreThemeName(appearance)
|
|
324
|
+
});
|
|
325
|
+
if (!highlighterOptionsByKey.has(cacheKey)) {
|
|
326
|
+
highlighterOptionsByKey.set(cacheKey, options);
|
|
327
|
+
}
|
|
328
|
+
return getSharedHighlighter({
|
|
329
|
+
...options,
|
|
330
|
+
preferredHighlighter: "shiki-wasm"
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
function queueHighlightedDiff(run) {
|
|
334
|
+
const queued = queuedHighlightWork.then(() => new Promise((resolve, reject) => {
|
|
335
|
+
queueMicrotask(() => {
|
|
336
|
+
try {
|
|
337
|
+
resolve(run());
|
|
338
|
+
} catch (error) {
|
|
339
|
+
reject(error);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}));
|
|
343
|
+
queuedHighlightWork = queued.then(() => {
|
|
344
|
+
return;
|
|
345
|
+
}, () => {
|
|
346
|
+
return;
|
|
347
|
+
});
|
|
348
|
+
return queued;
|
|
349
|
+
}
|
|
350
|
+
function aliasHighlightedContextLines(file, highlighted) {
|
|
351
|
+
for (const hunk of file.metadata.hunks) {
|
|
352
|
+
let deletionLineIndex = hunk.deletionLineIndex;
|
|
353
|
+
let additionLineIndex = hunk.additionLineIndex;
|
|
354
|
+
for (const content of hunk.hunkContent) {
|
|
355
|
+
if (content.type === "context") {
|
|
356
|
+
for (let offset = 0;offset < content.lines; offset += 1) {
|
|
357
|
+
const sharedLine = highlighted.additionLines[additionLineIndex + offset] ?? highlighted.deletionLines[deletionLineIndex + offset];
|
|
358
|
+
if (!sharedLine) {
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
highlighted.deletionLines[deletionLineIndex + offset] = sharedLine;
|
|
362
|
+
highlighted.additionLines[additionLineIndex + offset] = sharedLine;
|
|
363
|
+
}
|
|
364
|
+
deletionLineIndex += content.lines;
|
|
365
|
+
additionLineIndex += content.lines;
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
deletionLineIndex += content.deletions;
|
|
369
|
+
additionLineIndex += content.additions;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return highlighted;
|
|
373
|
+
}
|
|
374
|
+
async function loadHighlightedDiff(file, appearance = "dark") {
|
|
375
|
+
try {
|
|
376
|
+
const highlighter = await prepareHighlighter(file.language, appearance);
|
|
377
|
+
return queueHighlightedDiff(() => {
|
|
378
|
+
const highlighted = renderDiffWithHighlighter(file.metadata, highlighter, pierreRenderOptions(appearance));
|
|
379
|
+
return aliasHighlightedContextLines(file, {
|
|
380
|
+
deletionLines: highlighted.code.deletionLines,
|
|
381
|
+
additionLines: highlighted.code.additionLines
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
} catch {
|
|
385
|
+
const highlighter = await prepareHighlighter("text", appearance);
|
|
386
|
+
return queueHighlightedDiff(() => {
|
|
387
|
+
const highlighted = renderDiffWithHighlighter({ ...file.metadata, lang: "text" }, highlighter, pierreRenderOptions(appearance));
|
|
388
|
+
return aliasHighlightedContextLines(file, {
|
|
389
|
+
deletionLines: highlighted.code.deletionLines,
|
|
390
|
+
additionLines: highlighted.code.additionLines
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
function buildSplitRows(file, highlighted, theme) {
|
|
396
|
+
const rows = [];
|
|
397
|
+
const deletionLines = highlighted?.deletionLines ?? [];
|
|
398
|
+
const additionLines = highlighted?.additionLines ?? [];
|
|
399
|
+
for (const [hunkIndex, hunk] of file.metadata.hunks.entries()) {
|
|
400
|
+
if (hunk.collapsedBefore > 0) {
|
|
401
|
+
rows.push({
|
|
402
|
+
type: "collapsed",
|
|
403
|
+
key: `${file.id}:collapsed:${hunkIndex}`,
|
|
404
|
+
fileId: file.id,
|
|
405
|
+
hunkIndex,
|
|
406
|
+
text: collapsedRowText(hunk.collapsedBefore)
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
rows.push({
|
|
410
|
+
type: "hunk-header",
|
|
411
|
+
key: `${file.id}:header:${hunkIndex}`,
|
|
412
|
+
fileId: file.id,
|
|
413
|
+
hunkIndex,
|
|
414
|
+
text: formatHunkHeader(hunk)
|
|
415
|
+
});
|
|
416
|
+
let deletionLineIndex = hunk.deletionLineIndex;
|
|
417
|
+
let additionLineIndex = hunk.additionLineIndex;
|
|
418
|
+
let deletionLineNumber = hunk.deletionStart;
|
|
419
|
+
let additionLineNumber = hunk.additionStart;
|
|
420
|
+
for (const content of hunk.hunkContent) {
|
|
421
|
+
if (content.type === "context") {
|
|
422
|
+
for (let offset = 0;offset < content.lines; offset += 1) {
|
|
423
|
+
rows.push({
|
|
424
|
+
type: "split-line",
|
|
425
|
+
key: `${file.id}:split:${hunkIndex}:context:${deletionLineIndex + offset}:${additionLineIndex + offset}`,
|
|
426
|
+
fileId: file.id,
|
|
427
|
+
hunkIndex,
|
|
428
|
+
left: makeSplitCell("context", deletionLineNumber + offset, file.metadata.deletionLines[deletionLineIndex + offset], deletionLines[deletionLineIndex + offset], theme),
|
|
429
|
+
right: makeSplitCell("context", additionLineNumber + offset, file.metadata.additionLines[additionLineIndex + offset], additionLines[additionLineIndex + offset], theme)
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
deletionLineIndex += content.lines;
|
|
433
|
+
additionLineIndex += content.lines;
|
|
434
|
+
deletionLineNumber += content.lines;
|
|
435
|
+
additionLineNumber += content.lines;
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
const pairedLines = Math.max(content.deletions, content.additions);
|
|
439
|
+
for (let offset = 0;offset < pairedLines; offset += 1) {
|
|
440
|
+
const hasDeletion = offset < content.deletions;
|
|
441
|
+
const hasAddition = offset < content.additions;
|
|
442
|
+
rows.push({
|
|
443
|
+
type: "split-line",
|
|
444
|
+
key: `${file.id}:split:${hunkIndex}:change:${deletionLineIndex + offset}:${additionLineIndex + offset}`,
|
|
445
|
+
fileId: file.id,
|
|
446
|
+
hunkIndex,
|
|
447
|
+
left: hasDeletion ? makeSplitCell("deletion", deletionLineNumber + offset, file.metadata.deletionLines[deletionLineIndex + offset], deletionLines[deletionLineIndex + offset], theme) : makeSplitCell("empty", undefined, undefined, undefined, theme),
|
|
448
|
+
right: hasAddition ? makeSplitCell("addition", additionLineNumber + offset, file.metadata.additionLines[additionLineIndex + offset], additionLines[additionLineIndex + offset], theme) : makeSplitCell("empty", undefined, undefined, undefined, theme)
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
deletionLineIndex += content.deletions;
|
|
452
|
+
additionLineIndex += content.additions;
|
|
453
|
+
deletionLineNumber += content.deletions;
|
|
454
|
+
additionLineNumber += content.additions;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const trailingLines = trailingCollapsedLines(file.metadata);
|
|
458
|
+
if (trailingLines > 0) {
|
|
459
|
+
rows.push({
|
|
460
|
+
type: "collapsed",
|
|
461
|
+
key: `${file.id}:collapsed:trailing`,
|
|
462
|
+
fileId: file.id,
|
|
463
|
+
hunkIndex: Math.max(file.metadata.hunks.length - 1, 0),
|
|
464
|
+
text: collapsedRowText(trailingLines)
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
return rows;
|
|
468
|
+
}
|
|
469
|
+
function buildStackRows(file, highlighted, theme) {
|
|
470
|
+
const rows = [];
|
|
471
|
+
const deletionLines = highlighted?.deletionLines ?? [];
|
|
472
|
+
const additionLines = highlighted?.additionLines ?? [];
|
|
473
|
+
for (const [hunkIndex, hunk] of file.metadata.hunks.entries()) {
|
|
474
|
+
if (hunk.collapsedBefore > 0) {
|
|
475
|
+
rows.push({
|
|
476
|
+
type: "collapsed",
|
|
477
|
+
key: `${file.id}:stack:collapsed:${hunkIndex}`,
|
|
478
|
+
fileId: file.id,
|
|
479
|
+
hunkIndex,
|
|
480
|
+
text: collapsedRowText(hunk.collapsedBefore)
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
rows.push({
|
|
484
|
+
type: "hunk-header",
|
|
485
|
+
key: `${file.id}:stack:header:${hunkIndex}`,
|
|
486
|
+
fileId: file.id,
|
|
487
|
+
hunkIndex,
|
|
488
|
+
text: formatHunkHeader(hunk)
|
|
489
|
+
});
|
|
490
|
+
let deletionLineIndex = hunk.deletionLineIndex;
|
|
491
|
+
let additionLineIndex = hunk.additionLineIndex;
|
|
492
|
+
let deletionLineNumber = hunk.deletionStart;
|
|
493
|
+
let additionLineNumber = hunk.additionStart;
|
|
494
|
+
for (const content of hunk.hunkContent) {
|
|
495
|
+
if (content.type === "context") {
|
|
496
|
+
for (let offset = 0;offset < content.lines; offset += 1) {
|
|
497
|
+
rows.push({
|
|
498
|
+
type: "stack-line",
|
|
499
|
+
key: `${file.id}:stack:${hunkIndex}:context:${deletionLineIndex + offset}:${additionLineIndex + offset}`,
|
|
500
|
+
fileId: file.id,
|
|
501
|
+
hunkIndex,
|
|
502
|
+
cell: makeStackCell("context", deletionLineNumber + offset, additionLineNumber + offset, file.metadata.additionLines[additionLineIndex + offset], additionLines[additionLineIndex + offset], theme)
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
deletionLineIndex += content.lines;
|
|
506
|
+
additionLineIndex += content.lines;
|
|
507
|
+
deletionLineNumber += content.lines;
|
|
508
|
+
additionLineNumber += content.lines;
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
for (let offset = 0;offset < content.deletions; offset += 1) {
|
|
512
|
+
rows.push({
|
|
513
|
+
type: "stack-line",
|
|
514
|
+
key: `${file.id}:stack:${hunkIndex}:deletion:${deletionLineIndex + offset}`,
|
|
515
|
+
fileId: file.id,
|
|
516
|
+
hunkIndex,
|
|
517
|
+
cell: makeStackCell("deletion", deletionLineNumber + offset, undefined, file.metadata.deletionLines[deletionLineIndex + offset], deletionLines[deletionLineIndex + offset], theme)
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
for (let offset = 0;offset < content.additions; offset += 1) {
|
|
521
|
+
rows.push({
|
|
522
|
+
type: "stack-line",
|
|
523
|
+
key: `${file.id}:stack:${hunkIndex}:addition:${additionLineIndex + offset}`,
|
|
524
|
+
fileId: file.id,
|
|
525
|
+
hunkIndex,
|
|
526
|
+
cell: makeStackCell("addition", undefined, additionLineNumber + offset, file.metadata.additionLines[additionLineIndex + offset], additionLines[additionLineIndex + offset], theme)
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
deletionLineIndex += content.deletions;
|
|
530
|
+
additionLineIndex += content.additions;
|
|
531
|
+
deletionLineNumber += content.deletions;
|
|
532
|
+
additionLineNumber += content.additions;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
const trailingLines = trailingCollapsedLines(file.metadata);
|
|
536
|
+
if (trailingLines > 0) {
|
|
537
|
+
rows.push({
|
|
538
|
+
type: "collapsed",
|
|
539
|
+
key: `${file.id}:stack:collapsed:trailing`,
|
|
540
|
+
fileId: file.id,
|
|
541
|
+
hunkIndex: Math.max(file.metadata.hunks.length - 1, 0),
|
|
542
|
+
text: collapsedRowText(trailingLines)
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
return rows;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/ui/diff/renderRows.tsx
|
|
549
|
+
import { memo } from "react";
|
|
550
|
+
|
|
551
|
+
// src/ui/diff/rowStyle.ts
|
|
552
|
+
var INACTIVE_RAIL_BLEND = 0.35;
|
|
553
|
+
function diffRailMarker() {
|
|
554
|
+
return "▌";
|
|
555
|
+
}
|
|
556
|
+
function neutralRailColor(theme) {
|
|
557
|
+
return theme.lineNumberFg;
|
|
558
|
+
}
|
|
559
|
+
function dimRailColor(color, theme) {
|
|
560
|
+
return blendHex(color, theme.panel, INACTIVE_RAIL_BLEND);
|
|
561
|
+
}
|
|
562
|
+
function stackRailColor(kind, theme, selected) {
|
|
563
|
+
let color;
|
|
564
|
+
if (kind === "addition") {
|
|
565
|
+
color = theme.addedSignColor;
|
|
566
|
+
} else if (kind === "deletion") {
|
|
567
|
+
color = theme.removedSignColor;
|
|
568
|
+
} else {
|
|
569
|
+
color = neutralRailColor(theme);
|
|
570
|
+
}
|
|
571
|
+
return selected ? color : dimRailColor(color, theme);
|
|
572
|
+
}
|
|
573
|
+
function splitLeftRailColor(kind, theme, selected) {
|
|
574
|
+
const color = kind === "deletion" ? theme.removedSignColor : neutralRailColor(theme);
|
|
575
|
+
return selected ? color : dimRailColor(color, theme);
|
|
576
|
+
}
|
|
577
|
+
function splitRightRailColor(kind, theme, selected) {
|
|
578
|
+
const color = kind === "addition" ? theme.addedSignColor : neutralRailColor(theme);
|
|
579
|
+
return selected ? color : dimRailColor(color, theme);
|
|
580
|
+
}
|
|
581
|
+
function splitCellPalette(kind, theme) {
|
|
582
|
+
if (kind === "addition") {
|
|
583
|
+
return {
|
|
584
|
+
gutterBg: theme.addedBg,
|
|
585
|
+
contentBg: theme.addedBg,
|
|
586
|
+
signColor: theme.addedSignColor,
|
|
587
|
+
numberColor: theme.addedSignColor
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
if (kind === "deletion") {
|
|
591
|
+
return {
|
|
592
|
+
gutterBg: theme.removedBg,
|
|
593
|
+
contentBg: theme.removedBg,
|
|
594
|
+
signColor: theme.removedSignColor,
|
|
595
|
+
numberColor: theme.removedSignColor
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
if (kind === "empty") {
|
|
599
|
+
return {
|
|
600
|
+
gutterBg: theme.lineNumberBg,
|
|
601
|
+
contentBg: theme.panelAlt,
|
|
602
|
+
signColor: theme.muted,
|
|
603
|
+
numberColor: theme.lineNumberFg
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
return {
|
|
607
|
+
gutterBg: theme.lineNumberBg,
|
|
608
|
+
contentBg: theme.contextBg,
|
|
609
|
+
signColor: theme.muted,
|
|
610
|
+
numberColor: theme.lineNumberFg
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
function stackCellPalette(kind, theme) {
|
|
614
|
+
if (kind === "addition") {
|
|
615
|
+
return {
|
|
616
|
+
gutterBg: theme.addedBg,
|
|
617
|
+
contentBg: theme.addedBg,
|
|
618
|
+
signColor: theme.addedSignColor,
|
|
619
|
+
numberColor: theme.addedSignColor
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
if (kind === "deletion") {
|
|
623
|
+
return {
|
|
624
|
+
gutterBg: theme.removedBg,
|
|
625
|
+
contentBg: theme.removedBg,
|
|
626
|
+
signColor: theme.removedSignColor,
|
|
627
|
+
numberColor: theme.removedSignColor
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
return {
|
|
631
|
+
gutterBg: theme.lineNumberBg,
|
|
632
|
+
contentBg: theme.contextBg,
|
|
633
|
+
signColor: theme.muted,
|
|
634
|
+
numberColor: theme.lineNumberFg
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function diffLineNumberText(value, width) {
|
|
638
|
+
return value === undefined ? " ".repeat(width) : String(value).padStart(width, " ");
|
|
639
|
+
}
|
|
640
|
+
function stackGutterText(cell, lineNumberDigits, showLineNumbers) {
|
|
641
|
+
if (!showLineNumbers) {
|
|
642
|
+
return `${cell.sign} `;
|
|
643
|
+
}
|
|
644
|
+
const oldNumber = diffLineNumberText(cell.oldLineNumber, lineNumberDigits);
|
|
645
|
+
const newNumber = diffLineNumberText(cell.newLineNumber, lineNumberDigits);
|
|
646
|
+
return `${oldNumber} ${newNumber} ${cell.sign}`;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// src/ui/diff/renderRows.tsx
|
|
650
|
+
import { jsxDEV, Fragment } from "@opentui/react/jsx-dev-runtime";
|
|
651
|
+
function fitText(text, width) {
|
|
652
|
+
if (width <= 0) {
|
|
653
|
+
return "";
|
|
654
|
+
}
|
|
655
|
+
if (text.length <= width) {
|
|
656
|
+
return text;
|
|
657
|
+
}
|
|
658
|
+
if (width === 1) {
|
|
659
|
+
return "…";
|
|
660
|
+
}
|
|
661
|
+
return `${text.slice(0, width - 1)}…`;
|
|
662
|
+
}
|
|
663
|
+
function sliceSpansWindow(spans, offset, width) {
|
|
664
|
+
if (width <= 0) {
|
|
665
|
+
return {
|
|
666
|
+
spans: [],
|
|
667
|
+
usedWidth: 0
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
const sliced = [];
|
|
671
|
+
let remainingOffset = Math.max(0, offset);
|
|
672
|
+
let remaining = width;
|
|
673
|
+
let usedWidth = 0;
|
|
674
|
+
for (const span of spans) {
|
|
675
|
+
if (remaining <= 0) {
|
|
676
|
+
break;
|
|
677
|
+
}
|
|
678
|
+
if (remainingOffset >= span.text.length) {
|
|
679
|
+
remainingOffset -= span.text.length;
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
const start = remainingOffset;
|
|
683
|
+
const text = span.text.slice(start, start + remaining);
|
|
684
|
+
remainingOffset = 0;
|
|
685
|
+
if (text.length === 0) {
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
const nextSpan = {
|
|
689
|
+
...span,
|
|
690
|
+
text
|
|
691
|
+
};
|
|
692
|
+
const previous = sliced.at(-1);
|
|
693
|
+
if (previous && previous.fg === nextSpan.fg && previous.bg === nextSpan.bg) {
|
|
694
|
+
previous.text += nextSpan.text;
|
|
695
|
+
} else {
|
|
696
|
+
sliced.push(nextSpan);
|
|
697
|
+
}
|
|
698
|
+
remaining -= text.length;
|
|
699
|
+
usedWidth += text.length;
|
|
700
|
+
}
|
|
701
|
+
return {
|
|
702
|
+
spans: sliced,
|
|
703
|
+
usedWidth
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
var marker = diffRailMarker;
|
|
707
|
+
function renderInlineSpans(spans, width, fallbackColor, fallbackBg, keyPrefix, horizontalOffset = 0) {
|
|
708
|
+
const { spans: trimmed, usedWidth } = sliceSpansWindow(spans, horizontalOffset, width);
|
|
709
|
+
let padding = Math.max(0, width - usedWidth);
|
|
710
|
+
if (padding > 0) {
|
|
711
|
+
const lastSpan = trimmed.at(-1);
|
|
712
|
+
if (lastSpan && (lastSpan.fg ?? fallbackColor) === fallbackColor && (lastSpan.bg ?? fallbackBg) === fallbackBg) {
|
|
713
|
+
lastSpan.text += " ".repeat(padding);
|
|
714
|
+
padding = 0;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return /* @__PURE__ */ jsxDEV(Fragment, {
|
|
718
|
+
children: [
|
|
719
|
+
trimmed.map((span, index) => /* @__PURE__ */ jsxDEV("span", {
|
|
720
|
+
fg: span.fg ?? fallbackColor,
|
|
721
|
+
bg: span.bg ?? fallbackBg,
|
|
722
|
+
children: span.text
|
|
723
|
+
}, `${keyPrefix}:${index}`, false, undefined, this)),
|
|
724
|
+
padding > 0 ? /* @__PURE__ */ jsxDEV("span", {
|
|
725
|
+
fg: fallbackColor,
|
|
726
|
+
bg: fallbackBg,
|
|
727
|
+
children: `${" ".repeat(padding)}`
|
|
728
|
+
}, `${keyPrefix}:padding`, false, undefined, this) : null
|
|
729
|
+
]
|
|
730
|
+
}, undefined, true, undefined, this);
|
|
731
|
+
}
|
|
732
|
+
function wrapSpans(spans, width) {
|
|
733
|
+
if (width <= 0) {
|
|
734
|
+
return [[]];
|
|
735
|
+
}
|
|
736
|
+
const lines = [[]];
|
|
737
|
+
let current = lines[0];
|
|
738
|
+
let remaining = width;
|
|
739
|
+
for (const span of spans) {
|
|
740
|
+
let offset = 0;
|
|
741
|
+
while (offset < span.text.length) {
|
|
742
|
+
if (remaining <= 0) {
|
|
743
|
+
current = [];
|
|
744
|
+
lines.push(current);
|
|
745
|
+
remaining = width;
|
|
746
|
+
}
|
|
747
|
+
const text = span.text.slice(offset, offset + remaining);
|
|
748
|
+
if (text.length === 0) {
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
const nextSpan = {
|
|
752
|
+
...span,
|
|
753
|
+
text
|
|
754
|
+
};
|
|
755
|
+
const previous = current.at(-1);
|
|
756
|
+
if (previous && previous.fg === nextSpan.fg && previous.bg === nextSpan.bg) {
|
|
757
|
+
previous.text += nextSpan.text;
|
|
758
|
+
} else {
|
|
759
|
+
current.push(nextSpan);
|
|
760
|
+
}
|
|
761
|
+
offset += text.length;
|
|
762
|
+
remaining -= text.length;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return lines;
|
|
766
|
+
}
|
|
767
|
+
function buildWrappedSplitCell(cell, width, lineNumberDigits, showLineNumbers, prefixWidth, theme) {
|
|
768
|
+
const palette = splitCellPalette(cell.kind, theme);
|
|
769
|
+
const { gutterWidth, contentWidth } = resolveSplitCellGeometry(width, lineNumberDigits, showLineNumbers, prefixWidth);
|
|
770
|
+
const firstGutterText = showLineNumbers ? `${cell.lineNumber ? String(cell.lineNumber).padStart(lineNumberDigits, " ") : " ".repeat(lineNumberDigits)} ${cell.sign}`.padEnd(gutterWidth) : `${cell.sign} `.padEnd(gutterWidth);
|
|
771
|
+
const wrappedSpans = wrapSpans(cell.spans, contentWidth);
|
|
772
|
+
return {
|
|
773
|
+
gutterWidth,
|
|
774
|
+
palette,
|
|
775
|
+
lines: wrappedSpans.map((spans, index) => ({
|
|
776
|
+
gutterText: index === 0 ? firstGutterText : " ".repeat(gutterWidth),
|
|
777
|
+
spans
|
|
778
|
+
}))
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
function buildWrappedStackCell(cell, width, lineNumberDigits, showLineNumbers, prefixWidth, theme) {
|
|
782
|
+
const palette = stackCellPalette(cell.kind, theme);
|
|
783
|
+
const { gutterWidth, contentWidth } = resolveStackCellGeometry(width, lineNumberDigits, showLineNumbers, prefixWidth);
|
|
784
|
+
const firstGutterText = stackGutterText(cell, lineNumberDigits, showLineNumbers).padEnd(gutterWidth);
|
|
785
|
+
const wrappedSpans = wrapSpans(cell.spans, contentWidth);
|
|
786
|
+
return {
|
|
787
|
+
gutterWidth,
|
|
788
|
+
palette,
|
|
789
|
+
lines: wrappedSpans.map((spans, index) => ({
|
|
790
|
+
gutterText: index === 0 ? firstGutterText : " ".repeat(gutterWidth),
|
|
791
|
+
spans
|
|
792
|
+
}))
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
function renderSplitCell(cell, width, lineNumberDigits, showLineNumbers, theme, keyPrefix, contentOffset = 0, prefix) {
|
|
796
|
+
const palette = splitCellPalette(cell.kind, theme);
|
|
797
|
+
const prefixWidth = prefix?.text.length ?? 0;
|
|
798
|
+
const { gutterWidth, contentWidth } = resolveSplitCellGeometry(width, lineNumberDigits, showLineNumbers, prefixWidth);
|
|
799
|
+
const gutterText = showLineNumbers ? `${cell.lineNumber ? String(cell.lineNumber).padStart(lineNumberDigits, " ") : " ".repeat(lineNumberDigits)} ${cell.sign}`.padEnd(gutterWidth) : `${cell.sign} `.padEnd(gutterWidth);
|
|
800
|
+
return /* @__PURE__ */ jsxDEV(Fragment, {
|
|
801
|
+
children: [
|
|
802
|
+
prefix ? /* @__PURE__ */ jsxDEV("span", {
|
|
803
|
+
fg: prefix.fg,
|
|
804
|
+
bg: prefix.bg,
|
|
805
|
+
children: prefix.text
|
|
806
|
+
}, `${keyPrefix}:prefix`, false, undefined, this) : null,
|
|
807
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
808
|
+
fg: palette.numberColor,
|
|
809
|
+
bg: palette.gutterBg,
|
|
810
|
+
children: gutterText
|
|
811
|
+
}, `${keyPrefix}:gutter`, false, undefined, this),
|
|
812
|
+
renderInlineSpans(cell.spans, contentWidth, theme.text, palette.contentBg, `${keyPrefix}:content`, contentOffset)
|
|
813
|
+
]
|
|
814
|
+
}, undefined, true, undefined, this);
|
|
815
|
+
}
|
|
816
|
+
function renderStackCell(cell, width, lineNumberDigits, showLineNumbers, theme, keyPrefix, contentOffset = 0, prefix) {
|
|
817
|
+
const palette = stackCellPalette(cell.kind, theme);
|
|
818
|
+
const prefixWidth = prefix?.text.length ?? 0;
|
|
819
|
+
const { gutterWidth, contentWidth } = resolveStackCellGeometry(width, lineNumberDigits, showLineNumbers, prefixWidth);
|
|
820
|
+
return /* @__PURE__ */ jsxDEV(Fragment, {
|
|
821
|
+
children: [
|
|
822
|
+
prefix ? /* @__PURE__ */ jsxDEV("span", {
|
|
823
|
+
fg: prefix.fg,
|
|
824
|
+
bg: prefix.bg,
|
|
825
|
+
children: prefix.text
|
|
826
|
+
}, `${keyPrefix}:prefix`, false, undefined, this) : null,
|
|
827
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
828
|
+
fg: palette.numberColor,
|
|
829
|
+
bg: palette.gutterBg,
|
|
830
|
+
children: stackGutterText(cell, lineNumberDigits, showLineNumbers).padEnd(gutterWidth)
|
|
831
|
+
}, `${keyPrefix}:gutter`, false, undefined, this),
|
|
832
|
+
renderInlineSpans(cell.spans, contentWidth, theme.text, palette.contentBg, `${keyPrefix}:content`, contentOffset)
|
|
833
|
+
]
|
|
834
|
+
}, undefined, true, undefined, this);
|
|
835
|
+
}
|
|
836
|
+
function renderWrappedSplitCellLine(line, palette, contentWidth, theme, keyPrefix, prefix) {
|
|
837
|
+
return /* @__PURE__ */ jsxDEV(Fragment, {
|
|
838
|
+
children: [
|
|
839
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
840
|
+
fg: prefix.fg,
|
|
841
|
+
bg: prefix.bg,
|
|
842
|
+
children: prefix.text
|
|
843
|
+
}, `${keyPrefix}:prefix`, false, undefined, this),
|
|
844
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
845
|
+
fg: palette.numberColor,
|
|
846
|
+
bg: palette.gutterBg,
|
|
847
|
+
children: line.gutterText
|
|
848
|
+
}, `${keyPrefix}:gutter`, false, undefined, this),
|
|
849
|
+
renderInlineSpans(line.spans, contentWidth, theme.text, palette.contentBg, `${keyPrefix}:content`)
|
|
850
|
+
]
|
|
851
|
+
}, undefined, true, undefined, this);
|
|
852
|
+
}
|
|
853
|
+
function renderWrappedStackCellLine(line, palette, contentWidth, theme, keyPrefix, prefix) {
|
|
854
|
+
return /* @__PURE__ */ jsxDEV(Fragment, {
|
|
855
|
+
children: [
|
|
856
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
857
|
+
fg: prefix.fg,
|
|
858
|
+
bg: prefix.bg,
|
|
859
|
+
children: prefix.text
|
|
860
|
+
}, `${keyPrefix}:prefix`, false, undefined, this),
|
|
861
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
862
|
+
fg: palette.numberColor,
|
|
863
|
+
bg: palette.gutterBg,
|
|
864
|
+
children: line.gutterText
|
|
865
|
+
}, `${keyPrefix}:gutter`, false, undefined, this),
|
|
866
|
+
renderInlineSpans(line.spans, contentWidth, theme.text, palette.contentBg, `${keyPrefix}:content`)
|
|
867
|
+
]
|
|
868
|
+
}, undefined, true, undefined, this);
|
|
869
|
+
}
|
|
870
|
+
function diffMessage(file) {
|
|
871
|
+
if (file.metadata.type === "rename-pure") {
|
|
872
|
+
return "No textual hunks. This change only renames the file.";
|
|
873
|
+
}
|
|
874
|
+
if (file.isBinary) {
|
|
875
|
+
return "Binary file skipped";
|
|
876
|
+
}
|
|
877
|
+
if (file.isTooLarge) {
|
|
878
|
+
return "File too large to render automatically.";
|
|
879
|
+
}
|
|
880
|
+
if (file.metadata.type === "new") {
|
|
881
|
+
return "No textual hunks. The file is marked as new.";
|
|
882
|
+
}
|
|
883
|
+
if (file.metadata.type === "deleted") {
|
|
884
|
+
return "No textual hunks. The file is marked as deleted.";
|
|
885
|
+
}
|
|
886
|
+
return "No textual hunks to render for this file.";
|
|
887
|
+
}
|
|
888
|
+
function renderHeaderRow(row, width, theme, selected, annotated, anchorId, onOpenAgentNotesAtHunk) {
|
|
889
|
+
const badgeText = annotated ? "[AI]" : "";
|
|
890
|
+
const badgeWidth = annotated ? badgeText.length + 1 : 0;
|
|
891
|
+
const label = row.type === "collapsed" ? fitText(`··· ${row.text} ···`, Math.max(0, width - 1 - badgeWidth)) : fitText(row.text, Math.max(0, width - 1 - badgeWidth));
|
|
892
|
+
if (!annotated) {
|
|
893
|
+
return /* @__PURE__ */ jsxDEV("box", {
|
|
894
|
+
id: anchorId,
|
|
895
|
+
style: {
|
|
896
|
+
width: "100%",
|
|
897
|
+
height: 1,
|
|
898
|
+
backgroundColor: theme.panelAlt
|
|
899
|
+
},
|
|
900
|
+
children: /* @__PURE__ */ jsxDEV("text", {
|
|
901
|
+
children: [
|
|
902
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
903
|
+
fg: selected ? neutralRailColor(theme) : dimRailColor(neutralRailColor(theme), theme),
|
|
904
|
+
bg: theme.panelAlt,
|
|
905
|
+
children: marker()
|
|
906
|
+
}, undefined, false, undefined, this),
|
|
907
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
908
|
+
fg: row.type === "collapsed" ? theme.muted : theme.badgeNeutral,
|
|
909
|
+
bg: theme.panelAlt,
|
|
910
|
+
children: label
|
|
911
|
+
}, undefined, false, undefined, this)
|
|
912
|
+
]
|
|
913
|
+
}, undefined, true, undefined, this)
|
|
914
|
+
}, row.key, false, undefined, this);
|
|
915
|
+
}
|
|
916
|
+
return /* @__PURE__ */ jsxDEV("box", {
|
|
917
|
+
id: anchorId,
|
|
918
|
+
style: {
|
|
919
|
+
width: "100%",
|
|
920
|
+
height: 1,
|
|
921
|
+
flexDirection: "row",
|
|
922
|
+
backgroundColor: theme.panelAlt
|
|
923
|
+
},
|
|
924
|
+
children: [
|
|
925
|
+
/* @__PURE__ */ jsxDEV("box", {
|
|
926
|
+
style: { width: Math.max(0, width - badgeWidth), height: 1 },
|
|
927
|
+
children: /* @__PURE__ */ jsxDEV("text", {
|
|
928
|
+
children: [
|
|
929
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
930
|
+
fg: selected ? neutralRailColor(theme) : dimRailColor(neutralRailColor(theme), theme),
|
|
931
|
+
bg: theme.panelAlt,
|
|
932
|
+
children: marker()
|
|
933
|
+
}, undefined, false, undefined, this),
|
|
934
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
935
|
+
fg: row.type === "collapsed" ? theme.muted : theme.badgeNeutral,
|
|
936
|
+
bg: theme.panelAlt,
|
|
937
|
+
children: label
|
|
938
|
+
}, undefined, false, undefined, this)
|
|
939
|
+
]
|
|
940
|
+
}, undefined, true, undefined, this)
|
|
941
|
+
}, undefined, false, undefined, this),
|
|
942
|
+
/* @__PURE__ */ jsxDEV("box", {
|
|
943
|
+
style: { width: badgeWidth, height: 1 },
|
|
944
|
+
onMouseUp: () => onOpenAgentNotesAtHunk?.(row.hunkIndex),
|
|
945
|
+
children: /* @__PURE__ */ jsxDEV("text", {
|
|
946
|
+
fg: theme.noteTitleText,
|
|
947
|
+
bg: theme.noteTitleBackground,
|
|
948
|
+
children: ` ${badgeText}`
|
|
949
|
+
}, undefined, false, undefined, this)
|
|
950
|
+
}, undefined, false, undefined, this)
|
|
951
|
+
]
|
|
952
|
+
}, row.key, true, undefined, this);
|
|
953
|
+
}
|
|
954
|
+
function renderRow(row, width, lineNumberDigits, showLineNumbers, showHunkHeaders, wrapLines, codeHorizontalOffset, theme, selected, annotated, anchorId, noteGuideSide, onOpenAgentNotesAtHunk) {
|
|
955
|
+
let baseRow;
|
|
956
|
+
if (row.type === "collapsed") {
|
|
957
|
+
baseRow = renderHeaderRow(row, width, theme, selected, annotated, anchorId, onOpenAgentNotesAtHunk);
|
|
958
|
+
} else if (row.type === "hunk-header") {
|
|
959
|
+
baseRow = showHunkHeaders ? renderHeaderRow(row, width, theme, selected, annotated, anchorId, onOpenAgentNotesAtHunk) : null;
|
|
960
|
+
} else if (row.type === "split-line") {
|
|
961
|
+
const guideOnOldSide = noteGuideSide === "old";
|
|
962
|
+
const guideOnNewSide = noteGuideSide === "new";
|
|
963
|
+
const { leftWidth, rightWidth } = resolveSplitPaneWidths(width);
|
|
964
|
+
const rightRenderWidth = Math.max(0, rightWidth - (guideOnNewSide ? 1 : 0));
|
|
965
|
+
const leftPrefix = {
|
|
966
|
+
text: guideOnOldSide ? "│" : marker(),
|
|
967
|
+
fg: guideOnOldSide ? theme.noteBorder : splitLeftRailColor(row.left.kind, theme, selected),
|
|
968
|
+
bg: theme.panel
|
|
969
|
+
};
|
|
970
|
+
const rightPrefix = {
|
|
971
|
+
text: "▌",
|
|
972
|
+
fg: splitRightRailColor(row.right.kind, theme, selected),
|
|
973
|
+
bg: theme.panel
|
|
974
|
+
};
|
|
975
|
+
if (!wrapLines) {
|
|
976
|
+
baseRow = /* @__PURE__ */ jsxDEV("box", {
|
|
977
|
+
id: anchorId,
|
|
978
|
+
style: { width: "100%", height: 1 },
|
|
979
|
+
children: /* @__PURE__ */ jsxDEV("text", {
|
|
980
|
+
children: [
|
|
981
|
+
renderSplitCell(row.left, leftWidth, lineNumberDigits, showLineNumbers, theme, `${row.key}:left`, codeHorizontalOffset, leftPrefix),
|
|
982
|
+
renderSplitCell(row.right, rightRenderWidth, lineNumberDigits, showLineNumbers, theme, `${row.key}:right`, codeHorizontalOffset, rightPrefix),
|
|
983
|
+
guideOnNewSide ? /* @__PURE__ */ jsxDEV("span", {
|
|
984
|
+
fg: theme.noteBorder,
|
|
985
|
+
children: "│"
|
|
986
|
+
}, `${row.key}:note-guide`, false, undefined, this) : null
|
|
987
|
+
]
|
|
988
|
+
}, undefined, true, undefined, this)
|
|
989
|
+
}, undefined, false, undefined, this);
|
|
990
|
+
} else {
|
|
991
|
+
const leftLayout = buildWrappedSplitCell(row.left, leftWidth, lineNumberDigits, showLineNumbers, leftPrefix.text.length, theme);
|
|
992
|
+
const rightLayout = buildWrappedSplitCell(row.right, rightRenderWidth, lineNumberDigits, showLineNumbers, rightPrefix.text.length, theme);
|
|
993
|
+
const leftContentWidth = Math.max(0, leftWidth - leftPrefix.text.length - leftLayout.gutterWidth);
|
|
994
|
+
const rightContentWidth = Math.max(0, rightRenderWidth - rightPrefix.text.length - rightLayout.gutterWidth);
|
|
995
|
+
const visualLineCount = Math.max(leftLayout.lines.length, rightLayout.lines.length);
|
|
996
|
+
baseRow = /* @__PURE__ */ jsxDEV("box", {
|
|
997
|
+
id: anchorId,
|
|
998
|
+
style: { width: "100%", flexDirection: "column" },
|
|
999
|
+
children: Array.from({ length: visualLineCount }, (_, index) => {
|
|
1000
|
+
const leftLine = leftLayout.lines[index] ?? {
|
|
1001
|
+
gutterText: " ".repeat(leftLayout.gutterWidth),
|
|
1002
|
+
spans: []
|
|
1003
|
+
};
|
|
1004
|
+
const rightLine = rightLayout.lines[index] ?? {
|
|
1005
|
+
gutterText: " ".repeat(rightLayout.gutterWidth),
|
|
1006
|
+
spans: []
|
|
1007
|
+
};
|
|
1008
|
+
return /* @__PURE__ */ jsxDEV("box", {
|
|
1009
|
+
style: { width: "100%", height: 1 },
|
|
1010
|
+
children: /* @__PURE__ */ jsxDEV("text", {
|
|
1011
|
+
children: [
|
|
1012
|
+
renderWrappedSplitCellLine(leftLine, leftLayout.palette, leftContentWidth, theme, `${row.key}:left:${index}`, leftPrefix),
|
|
1013
|
+
renderWrappedSplitCellLine(rightLine, rightLayout.palette, rightContentWidth, theme, `${row.key}:right:${index}`, rightPrefix),
|
|
1014
|
+
guideOnNewSide ? /* @__PURE__ */ jsxDEV("span", {
|
|
1015
|
+
fg: theme.noteBorder,
|
|
1016
|
+
children: "│"
|
|
1017
|
+
}, `${row.key}:note-guide:${index}`, false, undefined, this) : null
|
|
1018
|
+
]
|
|
1019
|
+
}, undefined, true, undefined, this)
|
|
1020
|
+
}, `${row.key}:wrap:${index}`, false, undefined, this);
|
|
1021
|
+
})
|
|
1022
|
+
}, undefined, false, undefined, this);
|
|
1023
|
+
}
|
|
1024
|
+
} else if (row.type === "stack-line") {
|
|
1025
|
+
const guideOnOldSide = noteGuideSide === "old";
|
|
1026
|
+
const guideOnNewSide = noteGuideSide === "new";
|
|
1027
|
+
const contentWidth = Math.max(0, width - (guideOnNewSide ? 1 : 0));
|
|
1028
|
+
const prefix = {
|
|
1029
|
+
text: guideOnOldSide ? "│" : marker(),
|
|
1030
|
+
fg: guideOnOldSide ? theme.noteBorder : stackRailColor(row.cell.kind, theme, selected),
|
|
1031
|
+
bg: theme.panel
|
|
1032
|
+
};
|
|
1033
|
+
if (!wrapLines) {
|
|
1034
|
+
baseRow = /* @__PURE__ */ jsxDEV("box", {
|
|
1035
|
+
id: anchorId,
|
|
1036
|
+
style: { width: "100%", height: 1 },
|
|
1037
|
+
children: /* @__PURE__ */ jsxDEV("text", {
|
|
1038
|
+
children: [
|
|
1039
|
+
renderStackCell(row.cell, contentWidth, lineNumberDigits, showLineNumbers, theme, `${row.key}:stack`, codeHorizontalOffset, prefix),
|
|
1040
|
+
guideOnNewSide ? /* @__PURE__ */ jsxDEV("span", {
|
|
1041
|
+
fg: theme.noteBorder,
|
|
1042
|
+
children: "│"
|
|
1043
|
+
}, `${row.key}:note-guide`, false, undefined, this) : null
|
|
1044
|
+
]
|
|
1045
|
+
}, undefined, true, undefined, this)
|
|
1046
|
+
}, undefined, false, undefined, this);
|
|
1047
|
+
} else {
|
|
1048
|
+
const layout = buildWrappedStackCell(row.cell, contentWidth, lineNumberDigits, showLineNumbers, prefix.text.length, theme);
|
|
1049
|
+
const wrappedContentWidth = Math.max(0, contentWidth - prefix.text.length - layout.gutterWidth);
|
|
1050
|
+
baseRow = /* @__PURE__ */ jsxDEV("box", {
|
|
1051
|
+
id: anchorId,
|
|
1052
|
+
style: { width: "100%", flexDirection: "column" },
|
|
1053
|
+
children: layout.lines.map((line, index) => /* @__PURE__ */ jsxDEV("box", {
|
|
1054
|
+
style: { width: "100%", height: 1 },
|
|
1055
|
+
children: /* @__PURE__ */ jsxDEV("text", {
|
|
1056
|
+
children: [
|
|
1057
|
+
renderWrappedStackCellLine(line, layout.palette, wrappedContentWidth, theme, `${row.key}:stack:${index}`, prefix),
|
|
1058
|
+
guideOnNewSide ? /* @__PURE__ */ jsxDEV("span", {
|
|
1059
|
+
fg: theme.noteBorder,
|
|
1060
|
+
children: "│"
|
|
1061
|
+
}, `${row.key}:note-guide:${index}`, false, undefined, this) : null
|
|
1062
|
+
]
|
|
1063
|
+
}, undefined, true, undefined, this)
|
|
1064
|
+
}, `${row.key}:wrap:${index}`, false, undefined, this))
|
|
1065
|
+
}, undefined, false, undefined, this);
|
|
1066
|
+
}
|
|
1067
|
+
} else {
|
|
1068
|
+
baseRow = /* @__PURE__ */ jsxDEV("box", {
|
|
1069
|
+
style: { width: "100%", height: 1 },
|
|
1070
|
+
children: /* @__PURE__ */ jsxDEV("text", {
|
|
1071
|
+
fg: theme.muted,
|
|
1072
|
+
children: "Unsupported row."
|
|
1073
|
+
}, undefined, false, undefined, this)
|
|
1074
|
+
}, undefined, false, undefined, this);
|
|
1075
|
+
}
|
|
1076
|
+
return baseRow;
|
|
1077
|
+
}
|
|
1078
|
+
var DiffRowView = memo(function DiffRowViewComponent({
|
|
1079
|
+
row,
|
|
1080
|
+
width,
|
|
1081
|
+
lineNumberDigits,
|
|
1082
|
+
showLineNumbers,
|
|
1083
|
+
showHunkHeaders,
|
|
1084
|
+
wrapLines,
|
|
1085
|
+
codeHorizontalOffset,
|
|
1086
|
+
theme,
|
|
1087
|
+
selected,
|
|
1088
|
+
annotated,
|
|
1089
|
+
anchorId,
|
|
1090
|
+
noteGuideSide,
|
|
1091
|
+
onOpenAgentNotesAtHunk
|
|
1092
|
+
}) {
|
|
1093
|
+
return renderRow(row, width, lineNumberDigits, showLineNumbers, showHunkHeaders, wrapLines, codeHorizontalOffset, theme, selected, annotated, anchorId, noteGuideSide, onOpenAgentNotesAtHunk);
|
|
1094
|
+
}, (previous, next) => {
|
|
1095
|
+
return previous.row === next.row && previous.width === next.width && previous.lineNumberDigits === next.lineNumberDigits && previous.showLineNumbers === next.showLineNumbers && previous.showHunkHeaders === next.showHunkHeaders && previous.wrapLines === next.wrapLines && previous.codeHorizontalOffset === next.codeHorizontalOffset && previous.theme === next.theme && previous.selected === next.selected && previous.annotated === next.annotated && previous.anchorId === next.anchorId && previous.noteGuideSide === next.noteGuideSide;
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
// src/ui/diff/useHighlightedDiff.ts
|
|
1099
|
+
import { useLayoutEffect, useState } from "react";
|
|
1100
|
+
var MAX_CACHE_ENTRIES = 150;
|
|
1101
|
+
var SHARED_HIGHLIGHTED_DIFF_CACHE = new Map;
|
|
1102
|
+
var SHARED_HIGHLIGHT_PROMISES = new Map;
|
|
1103
|
+
function enforceCacheLimit() {
|
|
1104
|
+
while (SHARED_HIGHLIGHTED_DIFF_CACHE.size > MAX_CACHE_ENTRIES) {
|
|
1105
|
+
const oldest = SHARED_HIGHLIGHTED_DIFF_CACHE.keys().next().value;
|
|
1106
|
+
if (oldest !== undefined) {
|
|
1107
|
+
SHARED_HIGHLIGHTED_DIFF_CACHE.delete(oldest);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
function lineSetFingerprint(lines) {
|
|
1112
|
+
let totalChars = 0;
|
|
1113
|
+
let hash = 2166136261;
|
|
1114
|
+
for (const line of lines ?? []) {
|
|
1115
|
+
totalChars += line.length;
|
|
1116
|
+
for (let index = 0;index < line.length; index += 1) {
|
|
1117
|
+
hash ^= line.charCodeAt(index);
|
|
1118
|
+
hash = Math.imul(hash, 16777619);
|
|
1119
|
+
}
|
|
1120
|
+
hash ^= 10;
|
|
1121
|
+
hash = Math.imul(hash, 16777619);
|
|
1122
|
+
}
|
|
1123
|
+
return `${lines?.length ?? 0}:${totalChars}:${(hash >>> 0).toString(36)}`;
|
|
1124
|
+
}
|
|
1125
|
+
function metadataFingerprint(file) {
|
|
1126
|
+
const hunkSummary = file.metadata.hunks.map((hunk) => `${hunk.hunkSpecs ?? ""}:${hunk.deletionStart}:${hunk.deletionCount}:${hunk.additionStart}:${hunk.additionCount}:${hunk.hunkContent.length}`).join("|");
|
|
1127
|
+
return [
|
|
1128
|
+
file.metadata.name,
|
|
1129
|
+
file.metadata.prevName ?? "",
|
|
1130
|
+
file.metadata.type,
|
|
1131
|
+
lineSetFingerprint(file.metadata.deletionLines),
|
|
1132
|
+
lineSetFingerprint(file.metadata.additionLines),
|
|
1133
|
+
hunkSummary
|
|
1134
|
+
].join(":");
|
|
1135
|
+
}
|
|
1136
|
+
function patchFingerprint(file) {
|
|
1137
|
+
const { patch } = file;
|
|
1138
|
+
if (patch.length === 0) {
|
|
1139
|
+
return metadataFingerprint(file);
|
|
1140
|
+
}
|
|
1141
|
+
const mid = Math.floor(patch.length / 2);
|
|
1142
|
+
return `${patch.length}:${patch.slice(0, 64)}:${patch.slice(mid, mid + 64)}:${patch.slice(-64)}`;
|
|
1143
|
+
}
|
|
1144
|
+
function buildCacheKey(appearance, file) {
|
|
1145
|
+
return `${appearance}:${file.id}:${patchFingerprint(file)}`;
|
|
1146
|
+
}
|
|
1147
|
+
function commitHighlightResult(cacheKey, promise, result) {
|
|
1148
|
+
if (SHARED_HIGHLIGHT_PROMISES.get(cacheKey) !== promise) {
|
|
1149
|
+
return false;
|
|
1150
|
+
}
|
|
1151
|
+
SHARED_HIGHLIGHT_PROMISES.delete(cacheKey);
|
|
1152
|
+
SHARED_HIGHLIGHTED_DIFF_CACHE.set(cacheKey, result);
|
|
1153
|
+
enforceCacheLimit();
|
|
1154
|
+
return true;
|
|
1155
|
+
}
|
|
1156
|
+
function ensureHighlightedDiffLoaded(file, appearance, cacheKey = buildCacheKey(appearance, file)) {
|
|
1157
|
+
const cached = SHARED_HIGHLIGHTED_DIFF_CACHE.get(cacheKey);
|
|
1158
|
+
if (cached) {
|
|
1159
|
+
return Promise.resolve(cached);
|
|
1160
|
+
}
|
|
1161
|
+
const existing = SHARED_HIGHLIGHT_PROMISES.get(cacheKey);
|
|
1162
|
+
if (existing) {
|
|
1163
|
+
return existing;
|
|
1164
|
+
}
|
|
1165
|
+
let pending;
|
|
1166
|
+
pending = loadHighlightedDiff(file, appearance).then((nextHighlighted) => {
|
|
1167
|
+
commitHighlightResult(cacheKey, pending, nextHighlighted);
|
|
1168
|
+
return nextHighlighted;
|
|
1169
|
+
}).catch(() => {
|
|
1170
|
+
const fallback = {
|
|
1171
|
+
deletionLines: [],
|
|
1172
|
+
additionLines: []
|
|
1173
|
+
};
|
|
1174
|
+
commitHighlightResult(cacheKey, pending, fallback);
|
|
1175
|
+
return fallback;
|
|
1176
|
+
});
|
|
1177
|
+
SHARED_HIGHLIGHT_PROMISES.set(cacheKey, pending);
|
|
1178
|
+
return pending;
|
|
1179
|
+
}
|
|
1180
|
+
function resolveHighlightedSnapshot({
|
|
1181
|
+
appearanceCacheKey,
|
|
1182
|
+
highlighted,
|
|
1183
|
+
highlightedCacheKey
|
|
1184
|
+
}) {
|
|
1185
|
+
if (!appearanceCacheKey) {
|
|
1186
|
+
return null;
|
|
1187
|
+
}
|
|
1188
|
+
if (highlightedCacheKey === appearanceCacheKey) {
|
|
1189
|
+
return highlighted;
|
|
1190
|
+
}
|
|
1191
|
+
return SHARED_HIGHLIGHTED_DIFF_CACHE.get(appearanceCacheKey) ?? null;
|
|
1192
|
+
}
|
|
1193
|
+
function useHighlightedDiff({
|
|
1194
|
+
file,
|
|
1195
|
+
appearance,
|
|
1196
|
+
shouldLoadHighlight
|
|
1197
|
+
}) {
|
|
1198
|
+
const [highlighted, setHighlighted] = useState(null);
|
|
1199
|
+
const [highlightedCacheKey, setHighlightedCacheKey] = useState(null);
|
|
1200
|
+
const appearanceCacheKey = file ? buildCacheKey(appearance, file) : null;
|
|
1201
|
+
useLayoutEffect(() => {
|
|
1202
|
+
if (!file || !appearanceCacheKey) {
|
|
1203
|
+
setHighlighted(null);
|
|
1204
|
+
setHighlightedCacheKey(null);
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
if (highlightedCacheKey === appearanceCacheKey) {
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
const cached = SHARED_HIGHLIGHTED_DIFF_CACHE.get(appearanceCacheKey);
|
|
1211
|
+
if (cached) {
|
|
1212
|
+
setHighlighted(cached);
|
|
1213
|
+
setHighlightedCacheKey(appearanceCacheKey);
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
if (!shouldLoadHighlight) {
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
let cancelled = false;
|
|
1220
|
+
setHighlighted(null);
|
|
1221
|
+
ensureHighlightedDiffLoaded(file, appearance, appearanceCacheKey).then((nextHighlighted) => {
|
|
1222
|
+
if (cancelled) {
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
setHighlighted(nextHighlighted);
|
|
1226
|
+
setHighlightedCacheKey(appearanceCacheKey);
|
|
1227
|
+
});
|
|
1228
|
+
return () => {
|
|
1229
|
+
cancelled = true;
|
|
1230
|
+
};
|
|
1231
|
+
}, [appearance, appearanceCacheKey, file, highlightedCacheKey, shouldLoadHighlight]);
|
|
1232
|
+
return resolveHighlightedSnapshot({
|
|
1233
|
+
appearanceCacheKey,
|
|
1234
|
+
highlighted,
|
|
1235
|
+
highlightedCacheKey
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// src/ui/themes.ts
|
|
1240
|
+
import { RGBA, SyntaxStyle } from "@opentui/core";
|
|
1241
|
+
function createSyntaxStyle(colors) {
|
|
1242
|
+
return SyntaxStyle.fromStyles({
|
|
1243
|
+
default: { fg: RGBA.fromHex(colors.default) },
|
|
1244
|
+
keyword: { fg: RGBA.fromHex(colors.keyword), bold: true },
|
|
1245
|
+
string: { fg: RGBA.fromHex(colors.string) },
|
|
1246
|
+
comment: { fg: RGBA.fromHex(colors.comment), italic: true },
|
|
1247
|
+
number: { fg: RGBA.fromHex(colors.number) },
|
|
1248
|
+
function: { fg: RGBA.fromHex(colors.function) },
|
|
1249
|
+
method: { fg: RGBA.fromHex(colors.function) },
|
|
1250
|
+
property: { fg: RGBA.fromHex(colors.property) },
|
|
1251
|
+
variable: { fg: RGBA.fromHex(colors.default) },
|
|
1252
|
+
constant: { fg: RGBA.fromHex(colors.number), bold: true },
|
|
1253
|
+
type: { fg: RGBA.fromHex(colors.type) },
|
|
1254
|
+
class: { fg: RGBA.fromHex(colors.type) },
|
|
1255
|
+
punctuation: { fg: RGBA.fromHex(colors.punctuation) }
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
function withLazySyntaxStyle(theme, syntaxColors) {
|
|
1259
|
+
let syntaxStyle = null;
|
|
1260
|
+
return {
|
|
1261
|
+
...theme,
|
|
1262
|
+
syntaxColors,
|
|
1263
|
+
get syntaxStyle() {
|
|
1264
|
+
syntaxStyle ??= createSyntaxStyle(syntaxColors);
|
|
1265
|
+
return syntaxStyle;
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
var THEMES = [
|
|
1270
|
+
withLazySyntaxStyle({
|
|
1271
|
+
id: "graphite",
|
|
1272
|
+
label: "Graphite",
|
|
1273
|
+
appearance: "dark",
|
|
1274
|
+
background: "#111315",
|
|
1275
|
+
panel: "#171a1d",
|
|
1276
|
+
panelAlt: "#1d2126",
|
|
1277
|
+
border: "#343c45",
|
|
1278
|
+
accent: "#d5e0ea",
|
|
1279
|
+
accentMuted: "#414a54",
|
|
1280
|
+
text: "#f2f4f6",
|
|
1281
|
+
muted: "#9aa4af",
|
|
1282
|
+
addedBg: "#1f3025",
|
|
1283
|
+
removedBg: "#372526",
|
|
1284
|
+
contextBg: "#181c20",
|
|
1285
|
+
addedContentBg: "#24362a",
|
|
1286
|
+
removedContentBg: "#432b2d",
|
|
1287
|
+
contextContentBg: "#1e2328",
|
|
1288
|
+
addedSignColor: "#88d39b",
|
|
1289
|
+
removedSignColor: "#f0a0a0",
|
|
1290
|
+
lineNumberBg: "#14181b",
|
|
1291
|
+
lineNumberFg: "#798592",
|
|
1292
|
+
selectedHunk: "#3b434b",
|
|
1293
|
+
badgeAdded: "#88d39b",
|
|
1294
|
+
badgeRemoved: "#f0a0a0",
|
|
1295
|
+
badgeNeutral: "#a9b4bf",
|
|
1296
|
+
fileNew: "#88d39b",
|
|
1297
|
+
fileDeleted: "#f0a0a0",
|
|
1298
|
+
fileRenamed: "#e6cf98",
|
|
1299
|
+
fileModified: "#c49bff",
|
|
1300
|
+
fileUntracked: "#7fd1ff",
|
|
1301
|
+
noteBorder: "#c6a0ff",
|
|
1302
|
+
noteBackground: "#241c31",
|
|
1303
|
+
noteTitleBackground: "#322446",
|
|
1304
|
+
noteTitleText: "#f5edff"
|
|
1305
|
+
}, {
|
|
1306
|
+
default: "#f2f4f6",
|
|
1307
|
+
keyword: "#c4d0da",
|
|
1308
|
+
string: "#d8c6ef",
|
|
1309
|
+
comment: "#7f8b97",
|
|
1310
|
+
number: "#e6cf98",
|
|
1311
|
+
function: "#dfe6ed",
|
|
1312
|
+
property: "#bac8d4",
|
|
1313
|
+
type: "#d3d9e2",
|
|
1314
|
+
punctuation: "#7f8b97"
|
|
1315
|
+
}),
|
|
1316
|
+
withLazySyntaxStyle({
|
|
1317
|
+
id: "midnight",
|
|
1318
|
+
label: "Midnight",
|
|
1319
|
+
appearance: "dark",
|
|
1320
|
+
background: "#08111f",
|
|
1321
|
+
panel: "#0e1b2e",
|
|
1322
|
+
panelAlt: "#13243a",
|
|
1323
|
+
border: "#284264",
|
|
1324
|
+
accent: "#7fd1ff",
|
|
1325
|
+
accentMuted: "#355578",
|
|
1326
|
+
text: "#eef4ff",
|
|
1327
|
+
muted: "#8da5c7",
|
|
1328
|
+
addedBg: "#153526",
|
|
1329
|
+
removedBg: "#47262a",
|
|
1330
|
+
contextBg: "#0f1b2d",
|
|
1331
|
+
addedContentBg: "#102a1f",
|
|
1332
|
+
removedContentBg: "#371b1e",
|
|
1333
|
+
contextContentBg: "#132238",
|
|
1334
|
+
addedSignColor: "#69d69a",
|
|
1335
|
+
removedSignColor: "#ff8e8e",
|
|
1336
|
+
lineNumberBg: "#0b1627",
|
|
1337
|
+
lineNumberFg: "#56739a",
|
|
1338
|
+
selectedHunk: "#20466a",
|
|
1339
|
+
badgeAdded: "#5ad188",
|
|
1340
|
+
badgeRemoved: "#ff8b8b",
|
|
1341
|
+
badgeNeutral: "#89a5d3",
|
|
1342
|
+
fileNew: "#5ad188",
|
|
1343
|
+
fileDeleted: "#ff8b8b",
|
|
1344
|
+
fileRenamed: "#ffd883",
|
|
1345
|
+
fileModified: "#b794f6",
|
|
1346
|
+
fileUntracked: "#7fd1ff",
|
|
1347
|
+
noteBorder: "#c49bff",
|
|
1348
|
+
noteBackground: "#211a36",
|
|
1349
|
+
noteTitleBackground: "#30234f",
|
|
1350
|
+
noteTitleText: "#f5eeff"
|
|
1351
|
+
}, {
|
|
1352
|
+
default: "#e8f1ff",
|
|
1353
|
+
keyword: "#8ed4ff",
|
|
1354
|
+
string: "#c7b4ff",
|
|
1355
|
+
comment: "#6e85a7",
|
|
1356
|
+
number: "#ffd883",
|
|
1357
|
+
function: "#b6c9ff",
|
|
1358
|
+
property: "#a8d6ff",
|
|
1359
|
+
type: "#a4b7ff",
|
|
1360
|
+
punctuation: "#6e85a7"
|
|
1361
|
+
}),
|
|
1362
|
+
withLazySyntaxStyle({
|
|
1363
|
+
id: "paper",
|
|
1364
|
+
label: "Paper",
|
|
1365
|
+
appearance: "light",
|
|
1366
|
+
background: "#f4efe6",
|
|
1367
|
+
panel: "#fffaf3",
|
|
1368
|
+
panelAlt: "#f8f1e7",
|
|
1369
|
+
border: "#d8c8b3",
|
|
1370
|
+
accent: "#77593a",
|
|
1371
|
+
accentMuted: "#d7ccbe",
|
|
1372
|
+
text: "#2f2417",
|
|
1373
|
+
muted: "#786753",
|
|
1374
|
+
addedBg: "#dff0e1",
|
|
1375
|
+
removedBg: "#f6ddde",
|
|
1376
|
+
contextBg: "#faf6ee",
|
|
1377
|
+
addedContentBg: "#eaf8ec",
|
|
1378
|
+
removedContentBg: "#fbebeb",
|
|
1379
|
+
contextContentBg: "#fffaf3",
|
|
1380
|
+
addedSignColor: "#3f8d58",
|
|
1381
|
+
removedSignColor: "#b4545b",
|
|
1382
|
+
lineNumberBg: "#f2e9dc",
|
|
1383
|
+
lineNumberFg: "#9b8367",
|
|
1384
|
+
selectedHunk: "#eadcc5",
|
|
1385
|
+
badgeAdded: "#3f8d58",
|
|
1386
|
+
badgeRemoved: "#b4545b",
|
|
1387
|
+
badgeNeutral: "#8e7355",
|
|
1388
|
+
fileNew: "#3f8d58",
|
|
1389
|
+
fileDeleted: "#b4545b",
|
|
1390
|
+
fileRenamed: "#9f6c1f",
|
|
1391
|
+
fileModified: "#7d5bc4",
|
|
1392
|
+
fileUntracked: "#4a6890",
|
|
1393
|
+
noteBorder: "#7d5bc4",
|
|
1394
|
+
noteBackground: "#efe6ff",
|
|
1395
|
+
noteTitleBackground: "#e3d7ff",
|
|
1396
|
+
noteTitleText: "#462b74"
|
|
1397
|
+
}, {
|
|
1398
|
+
default: "#2f2417",
|
|
1399
|
+
keyword: "#7b5a35",
|
|
1400
|
+
string: "#4a6890",
|
|
1401
|
+
comment: "#8f7a65",
|
|
1402
|
+
number: "#9f6c1f",
|
|
1403
|
+
function: "#5a4a8e",
|
|
1404
|
+
property: "#356b7f",
|
|
1405
|
+
type: "#5f5f9a",
|
|
1406
|
+
punctuation: "#8f7a65"
|
|
1407
|
+
}),
|
|
1408
|
+
withLazySyntaxStyle({
|
|
1409
|
+
id: "ember",
|
|
1410
|
+
label: "Ember",
|
|
1411
|
+
appearance: "dark",
|
|
1412
|
+
background: "#140b08",
|
|
1413
|
+
panel: "#22120d",
|
|
1414
|
+
panelAlt: "#2c1710",
|
|
1415
|
+
border: "#643627",
|
|
1416
|
+
accent: "#ffb07a",
|
|
1417
|
+
accentMuted: "#5d3428",
|
|
1418
|
+
text: "#fff0e6",
|
|
1419
|
+
muted: "#c7a18d",
|
|
1420
|
+
addedBg: "#183424",
|
|
1421
|
+
removedBg: "#4a1f1f",
|
|
1422
|
+
contextBg: "#24140e",
|
|
1423
|
+
addedContentBg: "#21432c",
|
|
1424
|
+
removedContentBg: "#5a2727",
|
|
1425
|
+
contextContentBg: "#2b1711",
|
|
1426
|
+
addedSignColor: "#83d99d",
|
|
1427
|
+
removedSignColor: "#ff9d8f",
|
|
1428
|
+
lineNumberBg: "#1c100c",
|
|
1429
|
+
lineNumberFg: "#9a735f",
|
|
1430
|
+
selectedHunk: "#6a3829",
|
|
1431
|
+
badgeAdded: "#83d99d",
|
|
1432
|
+
badgeRemoved: "#ff9d8f",
|
|
1433
|
+
badgeNeutral: "#f1be9d",
|
|
1434
|
+
fileNew: "#83d99d",
|
|
1435
|
+
fileDeleted: "#ff9d8f",
|
|
1436
|
+
fileRenamed: "#ffd08f",
|
|
1437
|
+
fileModified: "#d8b4fe",
|
|
1438
|
+
fileUntracked: "#ffb07a",
|
|
1439
|
+
noteBorder: "#e1a3ff",
|
|
1440
|
+
noteBackground: "#311d36",
|
|
1441
|
+
noteTitleBackground: "#452650",
|
|
1442
|
+
noteTitleText: "#fff0ff"
|
|
1443
|
+
}, {
|
|
1444
|
+
default: "#fff0e6",
|
|
1445
|
+
keyword: "#ffb47f",
|
|
1446
|
+
string: "#ffd3a8",
|
|
1447
|
+
comment: "#a17d69",
|
|
1448
|
+
number: "#ffd08f",
|
|
1449
|
+
function: "#ffd9b3",
|
|
1450
|
+
property: "#ffc89f",
|
|
1451
|
+
type: "#f7c5b0",
|
|
1452
|
+
punctuation: "#a17d69"
|
|
1453
|
+
})
|
|
1454
|
+
];
|
|
1455
|
+
function resolveTheme(requested, _themeMode) {
|
|
1456
|
+
const exact = THEMES.find((theme) => theme.id === requested);
|
|
1457
|
+
if (exact) {
|
|
1458
|
+
return exact;
|
|
1459
|
+
}
|
|
1460
|
+
return THEMES.find((theme) => theme.id === "graphite") ?? THEMES[0];
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
// src/opentui/model.ts
|
|
1464
|
+
import { parsePatchFiles } from "@pierre/diffs";
|
|
1465
|
+
|
|
1466
|
+
// src/core/binary.ts
|
|
1467
|
+
function patchLooksBinary(patch) {
|
|
1468
|
+
return /(^|\n)Binary files .* differ(?:\n|$)/.test(patch) || patch.includes(`
|
|
1469
|
+
GIT binary patch
|
|
1470
|
+
`);
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// src/core/diffPaths.ts
|
|
1474
|
+
function normalizeDiffPath(path) {
|
|
1475
|
+
return path?.replace(/[\r\n]+$/u, "");
|
|
1476
|
+
}
|
|
1477
|
+
function normalizeDiffMetadataPaths(metadata) {
|
|
1478
|
+
const name = normalizeDiffPath(metadata.name) ?? metadata.name;
|
|
1479
|
+
const prevName = normalizeDiffPath(metadata.prevName);
|
|
1480
|
+
if (name === metadata.name && prevName === metadata.prevName) {
|
|
1481
|
+
return metadata;
|
|
1482
|
+
}
|
|
1483
|
+
return {
|
|
1484
|
+
...metadata,
|
|
1485
|
+
name,
|
|
1486
|
+
prevName
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
// src/opentui/model.ts
|
|
1491
|
+
var NORMALIZED_HUNK_DIFF_FILES = new WeakSet;
|
|
1492
|
+
function splitPatchIntoFileChunks(rawPatch) {
|
|
1493
|
+
const patch = rawPatch.replaceAll(`\r
|
|
1494
|
+
`, `
|
|
1495
|
+
`);
|
|
1496
|
+
const lines = patch.split(`
|
|
1497
|
+
`);
|
|
1498
|
+
const chunks = [];
|
|
1499
|
+
let current = [];
|
|
1500
|
+
const hasGitHeaders = lines.some((line) => line.startsWith("diff --git "));
|
|
1501
|
+
const flush = () => {
|
|
1502
|
+
if (current.length > 0) {
|
|
1503
|
+
chunks.push(`${current.join(`
|
|
1504
|
+
`).trimEnd()}
|
|
1505
|
+
`);
|
|
1506
|
+
current = [];
|
|
1507
|
+
}
|
|
1508
|
+
};
|
|
1509
|
+
for (let index = 0;index < lines.length; index += 1) {
|
|
1510
|
+
const line = lines[index];
|
|
1511
|
+
if (hasGitHeaders && line.startsWith("diff --git ")) {
|
|
1512
|
+
flush();
|
|
1513
|
+
current.push(line);
|
|
1514
|
+
continue;
|
|
1515
|
+
}
|
|
1516
|
+
if (!hasGitHeaders && line.startsWith("--- ") && lines[index + 1]?.startsWith("+++ ")) {
|
|
1517
|
+
flush();
|
|
1518
|
+
current.push(line);
|
|
1519
|
+
current.push(lines[index + 1]);
|
|
1520
|
+
index += 1;
|
|
1521
|
+
continue;
|
|
1522
|
+
}
|
|
1523
|
+
if (current.length > 0) {
|
|
1524
|
+
current.push(line);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
flush();
|
|
1528
|
+
return chunks;
|
|
1529
|
+
}
|
|
1530
|
+
function findPatchChunk(metadata, chunks, index) {
|
|
1531
|
+
const byIndex = chunks[index];
|
|
1532
|
+
if (byIndex) {
|
|
1533
|
+
return byIndex;
|
|
1534
|
+
}
|
|
1535
|
+
const paths = [metadata.name, metadata.prevName].map(normalizeDiffPath).filter((value) => Boolean(value));
|
|
1536
|
+
return chunks.find((chunk) => paths.some((path) => chunk.includes(path))) ?? "";
|
|
1537
|
+
}
|
|
1538
|
+
function countHunkDiffStats(metadata) {
|
|
1539
|
+
let additions = 0;
|
|
1540
|
+
let deletions = 0;
|
|
1541
|
+
for (const hunk of metadata.hunks) {
|
|
1542
|
+
for (const content of hunk.hunkContent) {
|
|
1543
|
+
if (content.type === "change") {
|
|
1544
|
+
additions += content.additions;
|
|
1545
|
+
deletions += content.deletions;
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
return { additions, deletions };
|
|
1550
|
+
}
|
|
1551
|
+
function createHunkDiffFile(input) {
|
|
1552
|
+
const metadata = normalizeDiffMetadataPaths(input.metadata);
|
|
1553
|
+
const path = normalizeDiffPath(input.path) ?? metadata.name;
|
|
1554
|
+
const previousPath = normalizeDiffPath(input.previousPath) ?? metadata.prevName;
|
|
1555
|
+
const normalized = {
|
|
1556
|
+
...input,
|
|
1557
|
+
id: input.id,
|
|
1558
|
+
metadata,
|
|
1559
|
+
path,
|
|
1560
|
+
previousPath,
|
|
1561
|
+
stats: input.stats ?? countHunkDiffStats(metadata)
|
|
1562
|
+
};
|
|
1563
|
+
NORMALIZED_HUNK_DIFF_FILES.add(normalized);
|
|
1564
|
+
return normalized;
|
|
1565
|
+
}
|
|
1566
|
+
function resolveHunkDiffFile(input) {
|
|
1567
|
+
if (NORMALIZED_HUNK_DIFF_FILES.has(input)) {
|
|
1568
|
+
return input;
|
|
1569
|
+
}
|
|
1570
|
+
return createHunkDiffFile(input);
|
|
1571
|
+
}
|
|
1572
|
+
function toInternalDiffFile(diff) {
|
|
1573
|
+
const normalized = resolveHunkDiffFile(diff);
|
|
1574
|
+
const patch = normalized.patch ?? "";
|
|
1575
|
+
return {
|
|
1576
|
+
agent: null,
|
|
1577
|
+
id: normalized.id,
|
|
1578
|
+
isBinary: normalized.isBinary ?? patchLooksBinary(patch),
|
|
1579
|
+
isTooLarge: normalized.isTooLarge,
|
|
1580
|
+
isUntracked: normalized.isUntracked,
|
|
1581
|
+
language: normalized.language,
|
|
1582
|
+
metadata: normalized.metadata,
|
|
1583
|
+
patch,
|
|
1584
|
+
path: normalized.path ?? normalized.metadata.name,
|
|
1585
|
+
previousPath: normalized.previousPath,
|
|
1586
|
+
stats: normalized.stats,
|
|
1587
|
+
statsTruncated: normalized.statsTruncated
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
function createHunkDiffFilesFromPatch(patchText, sourceId = "patch") {
|
|
1591
|
+
const chunks = splitPatchIntoFileChunks(patchText);
|
|
1592
|
+
return parsePatchFiles(patchText, sourceId, true).flatMap((entry) => entry.files).map((metadata, index) => createHunkDiffFile({
|
|
1593
|
+
id: `${sourceId}:${index}:${normalizeDiffPath(metadata.name) ?? metadata.name}`,
|
|
1594
|
+
metadata,
|
|
1595
|
+
patch: findPatchChunk(metadata, chunks, index)
|
|
1596
|
+
}));
|
|
1597
|
+
}
|
|
1598
|
+
function toInternalDiffFiles(files) {
|
|
1599
|
+
return files.map(toInternalDiffFile);
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
// src/opentui/HunkDiffBody.tsx
|
|
1603
|
+
import { jsxDEV as jsxDEV2 } from "@opentui/react/jsx-dev-runtime";
|
|
1604
|
+
var EMPTY_ANNOTATED_HUNK_INDICES = new Set;
|
|
1605
|
+
function HunkDiffBody({
|
|
1606
|
+
file,
|
|
1607
|
+
layout = "split",
|
|
1608
|
+
width,
|
|
1609
|
+
theme = "graphite",
|
|
1610
|
+
showLineNumbers = true,
|
|
1611
|
+
showHunkHeaders = true,
|
|
1612
|
+
wrapLines = false,
|
|
1613
|
+
horizontalOffset = 0,
|
|
1614
|
+
highlight = true,
|
|
1615
|
+
selectedHunkIndex = 0
|
|
1616
|
+
}) {
|
|
1617
|
+
const resolvedTheme = resolveTheme(theme, null);
|
|
1618
|
+
const internalFile = useMemo(() => file ? toInternalDiffFile(file) : undefined, [file]);
|
|
1619
|
+
const resolvedHighlighted = useHighlightedDiff({
|
|
1620
|
+
file: internalFile,
|
|
1621
|
+
appearance: resolvedTheme.appearance,
|
|
1622
|
+
shouldLoadHighlight: highlight
|
|
1623
|
+
});
|
|
1624
|
+
const rows = useMemo(() => internalFile ? layout === "split" ? buildSplitRows(internalFile, resolvedHighlighted, resolvedTheme) : buildStackRows(internalFile, resolvedHighlighted, resolvedTheme) : [], [internalFile, layout, resolvedHighlighted, resolvedTheme]);
|
|
1625
|
+
const lineNumberDigits = useMemo(() => String(internalFile ? findMaxLineNumber(internalFile) : 1).length, [internalFile]);
|
|
1626
|
+
if (!internalFile) {
|
|
1627
|
+
return /* @__PURE__ */ jsxDEV2("box", {
|
|
1628
|
+
style: { width: "100%", paddingLeft: 1, paddingRight: 1 },
|
|
1629
|
+
children: /* @__PURE__ */ jsxDEV2("text", {
|
|
1630
|
+
fg: resolvedTheme.muted,
|
|
1631
|
+
children: fitText("No file selected.", Math.max(1, width - 2))
|
|
1632
|
+
}, undefined, false, undefined, this)
|
|
1633
|
+
}, undefined, false, undefined, this);
|
|
1634
|
+
}
|
|
1635
|
+
if (internalFile.metadata.hunks.length === 0) {
|
|
1636
|
+
return /* @__PURE__ */ jsxDEV2("box", {
|
|
1637
|
+
style: { width: "100%", paddingLeft: 1, paddingRight: 1, paddingBottom: 1 },
|
|
1638
|
+
children: /* @__PURE__ */ jsxDEV2("text", {
|
|
1639
|
+
fg: resolvedTheme.muted,
|
|
1640
|
+
children: fitText(diffMessage(internalFile), Math.max(1, width - 2))
|
|
1641
|
+
}, undefined, false, undefined, this)
|
|
1642
|
+
}, undefined, false, undefined, this);
|
|
1643
|
+
}
|
|
1644
|
+
return /* @__PURE__ */ jsxDEV2("box", {
|
|
1645
|
+
style: { width: "100%", flexDirection: "column" },
|
|
1646
|
+
children: rows.map((row) => /* @__PURE__ */ jsxDEV2("box", {
|
|
1647
|
+
style: { width: "100%", flexDirection: "column" },
|
|
1648
|
+
children: /* @__PURE__ */ jsxDEV2(DiffRowView, {
|
|
1649
|
+
row,
|
|
1650
|
+
width,
|
|
1651
|
+
lineNumberDigits,
|
|
1652
|
+
showLineNumbers,
|
|
1653
|
+
showHunkHeaders,
|
|
1654
|
+
wrapLines,
|
|
1655
|
+
codeHorizontalOffset: horizontalOffset,
|
|
1656
|
+
theme: resolvedTheme,
|
|
1657
|
+
selected: row.hunkIndex === selectedHunkIndex,
|
|
1658
|
+
annotated: EMPTY_ANNOTATED_HUNK_INDICES.has(row.hunkIndex)
|
|
1659
|
+
}, undefined, false, undefined, this)
|
|
1660
|
+
}, row.key, false, undefined, this))
|
|
1661
|
+
}, undefined, false, undefined, this);
|
|
1662
|
+
}
|
|
1663
|
+
// src/opentui/HunkDiffFileHeader.tsx
|
|
1664
|
+
import { useMemo as useMemo2 } from "react";
|
|
1665
|
+
|
|
1666
|
+
// src/ui/lib/files.ts
|
|
1667
|
+
import { basename, dirname } from "node:path/posix";
|
|
1668
|
+
function sidebarFileName(file) {
|
|
1669
|
+
const path = normalizeDiffPath(file.path) ?? file.path;
|
|
1670
|
+
const previousPath = normalizeDiffPath(file.previousPath);
|
|
1671
|
+
if (!previousPath || previousPath === path) {
|
|
1672
|
+
return basename(path);
|
|
1673
|
+
}
|
|
1674
|
+
const previousName = basename(previousPath);
|
|
1675
|
+
const nextName = basename(path);
|
|
1676
|
+
return previousName === nextName ? nextName : `${previousName} -> ${nextName}`;
|
|
1677
|
+
}
|
|
1678
|
+
function formatSidebarStat(prefix, value, truncated = false) {
|
|
1679
|
+
return value > 0 ? `${prefix}${value}${truncated ? "+" : ""}` : null;
|
|
1680
|
+
}
|
|
1681
|
+
function sidebarEntryStats(entry) {
|
|
1682
|
+
const stats = [];
|
|
1683
|
+
if (entry.agentCommentsText) {
|
|
1684
|
+
stats.push({ kind: "agent-comment", text: entry.agentCommentsText });
|
|
1685
|
+
}
|
|
1686
|
+
if (entry.additionsText) {
|
|
1687
|
+
stats.push({ kind: "addition", text: entry.additionsText });
|
|
1688
|
+
}
|
|
1689
|
+
if (entry.deletionsText) {
|
|
1690
|
+
stats.push({ kind: "deletion", text: entry.deletionsText });
|
|
1691
|
+
}
|
|
1692
|
+
return stats;
|
|
1693
|
+
}
|
|
1694
|
+
function sidebarEntryStatsWidth(entry) {
|
|
1695
|
+
return sidebarEntryStats(entry).reduce((width, stat, index) => width + stat.text.length + (index > 0 ? 1 : 0), 0);
|
|
1696
|
+
}
|
|
1697
|
+
function buildSidebarEntries(files) {
|
|
1698
|
+
const entries = [];
|
|
1699
|
+
let activeGroup = null;
|
|
1700
|
+
files.forEach((file, index) => {
|
|
1701
|
+
const path = normalizeDiffPath(file.path) ?? file.path;
|
|
1702
|
+
const group = dirname(path);
|
|
1703
|
+
const nextGroup = group === "." ? null : group;
|
|
1704
|
+
if (nextGroup !== activeGroup) {
|
|
1705
|
+
activeGroup = nextGroup;
|
|
1706
|
+
if (activeGroup) {
|
|
1707
|
+
entries.push({
|
|
1708
|
+
kind: "group",
|
|
1709
|
+
id: `group:${activeGroup}:${index}`,
|
|
1710
|
+
label: `${activeGroup}/`
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
const agentCommentCount = file.agent?.annotations.length ?? 0;
|
|
1715
|
+
entries.push({
|
|
1716
|
+
kind: "file",
|
|
1717
|
+
id: file.id,
|
|
1718
|
+
name: sidebarFileName(file),
|
|
1719
|
+
agentCommentsText: agentCommentCount > 0 ? `*${agentCommentCount}` : null,
|
|
1720
|
+
additionsText: formatSidebarStat("+", file.stats.additions, file.statsTruncated),
|
|
1721
|
+
deletionsText: formatSidebarStat("-", file.stats.deletions),
|
|
1722
|
+
changeType: file.metadata.type,
|
|
1723
|
+
isUntracked: file.isUntracked ?? false
|
|
1724
|
+
});
|
|
1725
|
+
});
|
|
1726
|
+
return entries;
|
|
1727
|
+
}
|
|
1728
|
+
function fileLabelParts(file) {
|
|
1729
|
+
if (!file) {
|
|
1730
|
+
return { filename: "No file selected", stateLabel: null };
|
|
1731
|
+
}
|
|
1732
|
+
const path = normalizeDiffPath(file.path) ?? file.path;
|
|
1733
|
+
const previousPath = normalizeDiffPath(file.previousPath);
|
|
1734
|
+
const baseLabel = previousPath && previousPath !== path ? `${previousPath} -> ${path}` : path;
|
|
1735
|
+
let stateLabel = null;
|
|
1736
|
+
if (file.isUntracked) {
|
|
1737
|
+
stateLabel = " (untracked)";
|
|
1738
|
+
} else if (file.metadata.type === "new") {
|
|
1739
|
+
stateLabel = " (new)";
|
|
1740
|
+
} else if (file.metadata.type === "deleted") {
|
|
1741
|
+
stateLabel = " (deleted)";
|
|
1742
|
+
}
|
|
1743
|
+
return { filename: baseLabel, stateLabel };
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// src/ui/lib/text.ts
|
|
1747
|
+
function fitText2(text, width) {
|
|
1748
|
+
if (width <= 0) {
|
|
1749
|
+
return "";
|
|
1750
|
+
}
|
|
1751
|
+
if (text.length <= width) {
|
|
1752
|
+
return text;
|
|
1753
|
+
}
|
|
1754
|
+
if (width === 1) {
|
|
1755
|
+
return ".";
|
|
1756
|
+
}
|
|
1757
|
+
return `${text.slice(0, width - 1)}.`;
|
|
1758
|
+
}
|
|
1759
|
+
function padText(text, width) {
|
|
1760
|
+
const trimmed = fitText2(text, width);
|
|
1761
|
+
return trimmed.padEnd(width, " ");
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
// src/ui/components/panes/DiffFileHeaderRow.tsx
|
|
1765
|
+
import { jsxDEV as jsxDEV3 } from "@opentui/react/jsx-dev-runtime";
|
|
1766
|
+
function DiffFileHeaderRow({
|
|
1767
|
+
file,
|
|
1768
|
+
headerLabelWidth,
|
|
1769
|
+
headerStatsWidth,
|
|
1770
|
+
theme,
|
|
1771
|
+
onSelect
|
|
1772
|
+
}) {
|
|
1773
|
+
const additionsText = `+${file.stats.additions}${file.statsTruncated ? "+" : ""}`;
|
|
1774
|
+
const deletionsText = `-${file.stats.deletions}`;
|
|
1775
|
+
const { filename, stateLabel } = fileLabelParts(file);
|
|
1776
|
+
return /* @__PURE__ */ jsxDEV3("box", {
|
|
1777
|
+
style: {
|
|
1778
|
+
width: "100%",
|
|
1779
|
+
height: 1,
|
|
1780
|
+
flexShrink: 0,
|
|
1781
|
+
flexDirection: "row",
|
|
1782
|
+
justifyContent: "space-between",
|
|
1783
|
+
paddingLeft: 1,
|
|
1784
|
+
paddingRight: 1,
|
|
1785
|
+
backgroundColor: theme.panel
|
|
1786
|
+
},
|
|
1787
|
+
onMouseUp: onSelect,
|
|
1788
|
+
children: [
|
|
1789
|
+
/* @__PURE__ */ jsxDEV3("box", {
|
|
1790
|
+
style: { flexDirection: "row" },
|
|
1791
|
+
children: [
|
|
1792
|
+
/* @__PURE__ */ jsxDEV3("text", {
|
|
1793
|
+
fg: theme.text,
|
|
1794
|
+
children: fitText2(filename, Math.max(1, headerLabelWidth - (stateLabel?.length ?? 0)))
|
|
1795
|
+
}, undefined, false, undefined, this),
|
|
1796
|
+
stateLabel && /* @__PURE__ */ jsxDEV3("text", {
|
|
1797
|
+
fg: theme.muted,
|
|
1798
|
+
children: stateLabel
|
|
1799
|
+
}, undefined, false, undefined, this)
|
|
1800
|
+
]
|
|
1801
|
+
}, undefined, true, undefined, this),
|
|
1802
|
+
/* @__PURE__ */ jsxDEV3("box", {
|
|
1803
|
+
style: {
|
|
1804
|
+
width: headerStatsWidth,
|
|
1805
|
+
height: 1,
|
|
1806
|
+
flexDirection: "row",
|
|
1807
|
+
justifyContent: "flex-end"
|
|
1808
|
+
},
|
|
1809
|
+
children: [
|
|
1810
|
+
/* @__PURE__ */ jsxDEV3("text", {
|
|
1811
|
+
fg: theme.badgeAdded,
|
|
1812
|
+
children: additionsText
|
|
1813
|
+
}, undefined, false, undefined, this),
|
|
1814
|
+
/* @__PURE__ */ jsxDEV3("text", {
|
|
1815
|
+
fg: theme.muted,
|
|
1816
|
+
children: " "
|
|
1817
|
+
}, undefined, false, undefined, this),
|
|
1818
|
+
/* @__PURE__ */ jsxDEV3("text", {
|
|
1819
|
+
fg: theme.badgeRemoved,
|
|
1820
|
+
children: deletionsText
|
|
1821
|
+
}, undefined, false, undefined, this)
|
|
1822
|
+
]
|
|
1823
|
+
}, undefined, true, undefined, this)
|
|
1824
|
+
]
|
|
1825
|
+
}, undefined, true, undefined, this);
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
// src/opentui/HunkDiffFileHeader.tsx
|
|
1829
|
+
import { jsxDEV as jsxDEV4 } from "@opentui/react/jsx-dev-runtime";
|
|
1830
|
+
function HunkDiffFileHeader({
|
|
1831
|
+
file,
|
|
1832
|
+
width,
|
|
1833
|
+
theme = "graphite",
|
|
1834
|
+
onSelect
|
|
1835
|
+
}) {
|
|
1836
|
+
const resolvedTheme = resolveTheme(theme, null);
|
|
1837
|
+
const internalFile = useMemo2(() => toInternalDiffFile(file), [file]);
|
|
1838
|
+
const headerStatsWidth = Math.max(7, `+${internalFile.stats.additions}${internalFile.statsTruncated ? "+" : ""} -${internalFile.stats.deletions}`.length);
|
|
1839
|
+
return /* @__PURE__ */ jsxDEV4(DiffFileHeaderRow, {
|
|
1840
|
+
file: internalFile,
|
|
1841
|
+
headerLabelWidth: Math.max(1, width - headerStatsWidth - 2),
|
|
1842
|
+
headerStatsWidth,
|
|
1843
|
+
theme: resolvedTheme,
|
|
1844
|
+
onSelect
|
|
1845
|
+
}, undefined, false, undefined, this);
|
|
1846
|
+
}
|
|
1847
|
+
// src/opentui/HunkDiffView.tsx
|
|
1848
|
+
import { jsxDEV as jsxDEV5 } from "@opentui/react/jsx-dev-runtime";
|
|
1849
|
+
function HunkDiffView({ diff, scrollable = true, ...props }) {
|
|
1850
|
+
const content = /* @__PURE__ */ jsxDEV5(HunkDiffBody, {
|
|
1851
|
+
file: diff,
|
|
1852
|
+
...props
|
|
1853
|
+
}, undefined, false, undefined, this);
|
|
1854
|
+
if (!scrollable) {
|
|
1855
|
+
return content;
|
|
1856
|
+
}
|
|
1857
|
+
return /* @__PURE__ */ jsxDEV5("scrollbox", {
|
|
1858
|
+
width: "100%",
|
|
1859
|
+
height: "100%",
|
|
1860
|
+
scrollY: true,
|
|
1861
|
+
viewportCulling: true,
|
|
1862
|
+
focused: false,
|
|
1863
|
+
children: content
|
|
1864
|
+
}, undefined, false, undefined, this);
|
|
1865
|
+
}
|
|
1866
|
+
// src/opentui/HunkFileNav.tsx
|
|
1867
|
+
import { useMemo as useMemo3 } from "react";
|
|
1868
|
+
|
|
1869
|
+
// src/ui/lib/ids.ts
|
|
1870
|
+
function fileRowId(fileId) {
|
|
1871
|
+
return `file-row:${fileId}`;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
// src/ui/components/panes/FileListItem.tsx
|
|
1875
|
+
import { jsxDEV as jsxDEV6 } from "@opentui/react/jsx-dev-runtime";
|
|
1876
|
+
function getFileStateIcon(entry, theme) {
|
|
1877
|
+
if (entry.isUntracked) {
|
|
1878
|
+
return { icon: "?", color: theme.fileUntracked };
|
|
1879
|
+
}
|
|
1880
|
+
switch (entry.changeType) {
|
|
1881
|
+
case "new":
|
|
1882
|
+
return { icon: "A", color: theme.fileNew };
|
|
1883
|
+
case "deleted":
|
|
1884
|
+
return { icon: "D", color: theme.fileDeleted };
|
|
1885
|
+
case "rename-pure":
|
|
1886
|
+
case "rename-changed":
|
|
1887
|
+
return { icon: "R", color: theme.fileRenamed };
|
|
1888
|
+
case "change":
|
|
1889
|
+
return { icon: "M", color: theme.fileModified };
|
|
1890
|
+
default:
|
|
1891
|
+
return { icon: "", color: theme.text };
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
function FileGroupHeader({
|
|
1895
|
+
entry,
|
|
1896
|
+
paddingLeft = 1,
|
|
1897
|
+
textWidth,
|
|
1898
|
+
theme
|
|
1899
|
+
}) {
|
|
1900
|
+
return /* @__PURE__ */ jsxDEV6("box", {
|
|
1901
|
+
style: {
|
|
1902
|
+
width: "100%",
|
|
1903
|
+
height: 1,
|
|
1904
|
+
paddingLeft,
|
|
1905
|
+
backgroundColor: theme.panel
|
|
1906
|
+
},
|
|
1907
|
+
children: /* @__PURE__ */ jsxDEV6("text", {
|
|
1908
|
+
fg: theme.muted,
|
|
1909
|
+
children: fitText2(entry.label, Math.max(1, textWidth))
|
|
1910
|
+
}, undefined, false, undefined, this)
|
|
1911
|
+
}, undefined, false, undefined, this);
|
|
1912
|
+
}
|
|
1913
|
+
function FileListItem({
|
|
1914
|
+
entry,
|
|
1915
|
+
paddingLeft = 1,
|
|
1916
|
+
selected,
|
|
1917
|
+
statsWidth,
|
|
1918
|
+
textWidth,
|
|
1919
|
+
theme,
|
|
1920
|
+
onSelect
|
|
1921
|
+
}) {
|
|
1922
|
+
const rowBackground = selected ? theme.panelAlt : theme.panel;
|
|
1923
|
+
const stats = sidebarEntryStats(entry);
|
|
1924
|
+
const { icon, color } = getFileStateIcon(entry, theme);
|
|
1925
|
+
const iconWidth = icon ? 2 : 0;
|
|
1926
|
+
const statsSectionWidth = statsWidth > 0 ? statsWidth + 1 : 0;
|
|
1927
|
+
const nameWidth = Math.max(1, textWidth - 1 - iconWidth - statsSectionWidth);
|
|
1928
|
+
return /* @__PURE__ */ jsxDEV6("box", {
|
|
1929
|
+
id: fileRowId(entry.id),
|
|
1930
|
+
style: {
|
|
1931
|
+
width: "100%",
|
|
1932
|
+
height: 1,
|
|
1933
|
+
backgroundColor: rowBackground,
|
|
1934
|
+
flexDirection: "row"
|
|
1935
|
+
},
|
|
1936
|
+
onMouseUp: onSelect,
|
|
1937
|
+
children: [
|
|
1938
|
+
/* @__PURE__ */ jsxDEV6("box", {
|
|
1939
|
+
style: {
|
|
1940
|
+
width: 1,
|
|
1941
|
+
height: 1,
|
|
1942
|
+
backgroundColor: selected ? theme.accent : rowBackground
|
|
1943
|
+
}
|
|
1944
|
+
}, undefined, false, undefined, this),
|
|
1945
|
+
/* @__PURE__ */ jsxDEV6("box", {
|
|
1946
|
+
style: {
|
|
1947
|
+
flexGrow: 1,
|
|
1948
|
+
height: 1,
|
|
1949
|
+
paddingLeft,
|
|
1950
|
+
flexDirection: "row",
|
|
1951
|
+
backgroundColor: rowBackground
|
|
1952
|
+
},
|
|
1953
|
+
children: [
|
|
1954
|
+
icon && /* @__PURE__ */ jsxDEV6("text", {
|
|
1955
|
+
fg: color,
|
|
1956
|
+
children: [
|
|
1957
|
+
icon,
|
|
1958
|
+
" "
|
|
1959
|
+
]
|
|
1960
|
+
}, undefined, true, undefined, this),
|
|
1961
|
+
/* @__PURE__ */ jsxDEV6("text", {
|
|
1962
|
+
fg: theme.text,
|
|
1963
|
+
children: padText(fitText2(entry.name, nameWidth), nameWidth)
|
|
1964
|
+
}, undefined, false, undefined, this),
|
|
1965
|
+
statsSectionWidth > 0 && /* @__PURE__ */ jsxDEV6("box", {
|
|
1966
|
+
style: {
|
|
1967
|
+
width: statsSectionWidth,
|
|
1968
|
+
height: 1,
|
|
1969
|
+
flexDirection: "row",
|
|
1970
|
+
justifyContent: "flex-end",
|
|
1971
|
+
backgroundColor: rowBackground
|
|
1972
|
+
},
|
|
1973
|
+
children: stats.map((stat, index) => /* @__PURE__ */ jsxDEV6("box", {
|
|
1974
|
+
style: { height: 1, flexDirection: "row", backgroundColor: rowBackground },
|
|
1975
|
+
children: [
|
|
1976
|
+
index > 0 && /* @__PURE__ */ jsxDEV6("text", {
|
|
1977
|
+
fg: selected ? theme.text : theme.muted,
|
|
1978
|
+
children: " "
|
|
1979
|
+
}, undefined, false, undefined, this),
|
|
1980
|
+
/* @__PURE__ */ jsxDEV6("text", {
|
|
1981
|
+
fg: stat.kind === "agent-comment" ? theme.noteBorder : stat.kind === "addition" ? theme.badgeAdded : theme.badgeRemoved,
|
|
1982
|
+
children: stat.text
|
|
1983
|
+
}, undefined, false, undefined, this)
|
|
1984
|
+
]
|
|
1985
|
+
}, `${entry.id}:${stat.kind}`, true, undefined, this))
|
|
1986
|
+
}, undefined, false, undefined, this)
|
|
1987
|
+
]
|
|
1988
|
+
}, undefined, true, undefined, this)
|
|
1989
|
+
]
|
|
1990
|
+
}, undefined, true, undefined, this);
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
// src/opentui/HunkFileNav.tsx
|
|
1994
|
+
import { jsxDEV as jsxDEV7 } from "@opentui/react/jsx-dev-runtime";
|
|
1995
|
+
function HunkFileNav({
|
|
1996
|
+
files,
|
|
1997
|
+
selectedFileId,
|
|
1998
|
+
width,
|
|
1999
|
+
theme = "graphite",
|
|
2000
|
+
onSelectFile = () => {}
|
|
2001
|
+
}) {
|
|
2002
|
+
const resolvedTheme = resolveTheme(theme, null);
|
|
2003
|
+
const internalFiles = useMemo3(() => toInternalDiffFiles(files), [files]);
|
|
2004
|
+
const entries = useMemo3(() => buildSidebarEntries(internalFiles), [internalFiles]);
|
|
2005
|
+
const fileEntries = entries.filter((entry) => entry.kind === "file");
|
|
2006
|
+
const statsWidth = Math.max(0, ...fileEntries.map((entry) => sidebarEntryStatsWidth(entry)));
|
|
2007
|
+
const textWidth = Math.max(1, width - 1);
|
|
2008
|
+
return /* @__PURE__ */ jsxDEV7("box", {
|
|
2009
|
+
style: { width: "100%", flexDirection: "column", backgroundColor: resolvedTheme.panel },
|
|
2010
|
+
children: entries.map((entry) => entry.kind === "group" ? /* @__PURE__ */ jsxDEV7(FileGroupHeader, {
|
|
2011
|
+
entry,
|
|
2012
|
+
paddingLeft: 0,
|
|
2013
|
+
textWidth: Math.max(1, width),
|
|
2014
|
+
theme: resolvedTheme
|
|
2015
|
+
}, entry.id, false, undefined, this) : /* @__PURE__ */ jsxDEV7(FileListItem, {
|
|
2016
|
+
entry,
|
|
2017
|
+
paddingLeft: 0,
|
|
2018
|
+
selected: entry.id === selectedFileId,
|
|
2019
|
+
statsWidth,
|
|
2020
|
+
textWidth,
|
|
2021
|
+
theme: resolvedTheme,
|
|
2022
|
+
onSelect: () => onSelectFile(entry.id)
|
|
2023
|
+
}, entry.id, false, undefined, this))
|
|
2024
|
+
}, undefined, false, undefined, this);
|
|
2025
|
+
}
|
|
2026
|
+
// src/opentui/HunkReviewStream.tsx
|
|
2027
|
+
import { jsxDEV as jsxDEV8 } from "@opentui/react/jsx-dev-runtime";
|
|
2028
|
+
function resolveSelection(files, selection) {
|
|
2029
|
+
if (selection && files.some((file) => file.id === selection.fileId)) {
|
|
2030
|
+
return selection;
|
|
2031
|
+
}
|
|
2032
|
+
const first = files[0];
|
|
2033
|
+
return first ? { fileId: first.id, hunkIndex: 0 } : undefined;
|
|
2034
|
+
}
|
|
2035
|
+
function HunkReviewStream({
|
|
2036
|
+
files,
|
|
2037
|
+
layout = "split",
|
|
2038
|
+
width,
|
|
2039
|
+
theme = "graphite",
|
|
2040
|
+
selection,
|
|
2041
|
+
showFileHeaders = true,
|
|
2042
|
+
showFileSeparators = true,
|
|
2043
|
+
showLineNumbers = true,
|
|
2044
|
+
showHunkHeaders = true,
|
|
2045
|
+
wrapLines = false,
|
|
2046
|
+
horizontalOffset = 0,
|
|
2047
|
+
highlight = true,
|
|
2048
|
+
onSelectionChange
|
|
2049
|
+
}) {
|
|
2050
|
+
const resolvedTheme = resolveTheme(theme, null);
|
|
2051
|
+
const activeSelection = resolveSelection(files, selection);
|
|
2052
|
+
if (files.length === 0) {
|
|
2053
|
+
return /* @__PURE__ */ jsxDEV8("box", {
|
|
2054
|
+
style: { width: "100%", paddingLeft: 1, paddingRight: 1 },
|
|
2055
|
+
children: /* @__PURE__ */ jsxDEV8("text", {
|
|
2056
|
+
fg: resolvedTheme.muted,
|
|
2057
|
+
children: "No files to render."
|
|
2058
|
+
}, undefined, false, undefined, this)
|
|
2059
|
+
}, undefined, false, undefined, this);
|
|
2060
|
+
}
|
|
2061
|
+
return /* @__PURE__ */ jsxDEV8("box", {
|
|
2062
|
+
style: { width: "100%", flexDirection: "column", backgroundColor: resolvedTheme.panel },
|
|
2063
|
+
children: files.map((file, index) => {
|
|
2064
|
+
const selectedHunkIndex = activeSelection?.fileId === file.id ? activeSelection.hunkIndex : -1;
|
|
2065
|
+
return /* @__PURE__ */ jsxDEV8("box", {
|
|
2066
|
+
style: {
|
|
2067
|
+
width: "100%",
|
|
2068
|
+
flexDirection: "column",
|
|
2069
|
+
backgroundColor: resolvedTheme.panel
|
|
2070
|
+
},
|
|
2071
|
+
children: [
|
|
2072
|
+
showFileSeparators && index > 0 ? /* @__PURE__ */ jsxDEV8("box", {
|
|
2073
|
+
style: { width: "100%", height: 1, paddingLeft: 1, paddingRight: 1 },
|
|
2074
|
+
children: /* @__PURE__ */ jsxDEV8("text", {
|
|
2075
|
+
fg: resolvedTheme.border,
|
|
2076
|
+
children: "─".repeat(Math.max(1, width - 2))
|
|
2077
|
+
}, undefined, false, undefined, this)
|
|
2078
|
+
}, undefined, false, undefined, this) : null,
|
|
2079
|
+
showFileHeaders ? /* @__PURE__ */ jsxDEV8(HunkDiffFileHeader, {
|
|
2080
|
+
file,
|
|
2081
|
+
width,
|
|
2082
|
+
theme,
|
|
2083
|
+
onSelect: () => onSelectionChange?.({ fileId: file.id, hunkIndex: 0 })
|
|
2084
|
+
}, undefined, false, undefined, this) : null,
|
|
2085
|
+
/* @__PURE__ */ jsxDEV8(HunkDiffBody, {
|
|
2086
|
+
file,
|
|
2087
|
+
layout,
|
|
2088
|
+
width,
|
|
2089
|
+
theme,
|
|
2090
|
+
showLineNumbers,
|
|
2091
|
+
showHunkHeaders,
|
|
2092
|
+
wrapLines,
|
|
2093
|
+
horizontalOffset,
|
|
2094
|
+
highlight,
|
|
2095
|
+
selectedHunkIndex
|
|
2096
|
+
}, undefined, false, undefined, this)
|
|
2097
|
+
]
|
|
2098
|
+
}, file.id, true, undefined, this);
|
|
2099
|
+
})
|
|
2100
|
+
}, undefined, false, undefined, this);
|
|
2101
|
+
}
|
|
2102
|
+
export {
|
|
2103
|
+
parsePatchFiles2 as parsePatchFiles,
|
|
2104
|
+
parseDiffFromFile,
|
|
2105
|
+
createHunkDiffFilesFromPatch,
|
|
2106
|
+
createHunkDiffFile,
|
|
2107
|
+
countHunkDiffStats,
|
|
2108
|
+
HunkReviewStream,
|
|
2109
|
+
HunkFileNav,
|
|
2110
|
+
HunkDiffView,
|
|
2111
|
+
HunkDiffFileHeader,
|
|
2112
|
+
HunkDiffBody,
|
|
2113
|
+
HUNK_DIFF_THEME_NAMES
|
|
2114
|
+
};
|