pi-cc-extensions 0.8.13 → 0.8.15
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/extensions/ask-user-question/state/build-questionnaire.ts +14 -2
- package/extensions/ask-user-question/view/components/preview/preview-layout-decider.ts +7 -0
- package/extensions/claude-code-style.ts +949 -72
- package/extensions/compact-style.ts +4 -2
- package/extensions/context.ts +53 -14
- package/extensions/session-reference.ts +86 -9
- package/extensions/tool-diff/diff-renderer.ts +220 -86
- package/extensions/tool-diff/index.ts +17 -4
- package/extensions/tool-diff/types.ts +7 -1
- package/extensions/tool-result-sanitize.ts +10 -3
- package/package.json +1 -1
|
@@ -277,29 +277,40 @@ function resolveLanguageFromPath(rawPath: string | undefined): string | undefine
|
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
+
const MAX_CODE_HIGHLIGHT_CACHE_ENTRIES = 2048;
|
|
281
|
+
|
|
280
282
|
function createCodeLineHighlighter(language: string | undefined): CodeLineHighlighter {
|
|
281
283
|
if (!language) {
|
|
282
284
|
return (line) => sanitizeAnsiForThemedOutput(line);
|
|
283
285
|
}
|
|
284
286
|
|
|
287
|
+
// Insertion-order Map used as a small LRU so large diffs cannot retain every unique line.
|
|
285
288
|
const cache = new Map<string, string>();
|
|
289
|
+
const remember = (line: string, value: string): string => {
|
|
290
|
+
if (cache.has(line)) cache.delete(line);
|
|
291
|
+
cache.set(line, value);
|
|
292
|
+
if (cache.size > MAX_CODE_HIGHLIGHT_CACHE_ENTRIES) {
|
|
293
|
+
const oldest = cache.keys().next().value;
|
|
294
|
+
if (oldest !== undefined) cache.delete(oldest);
|
|
295
|
+
}
|
|
296
|
+
return value;
|
|
297
|
+
};
|
|
286
298
|
return (line) => {
|
|
287
299
|
if (!line) {
|
|
288
300
|
return line;
|
|
289
301
|
}
|
|
290
302
|
const cached = cache.get(line);
|
|
291
303
|
if (cached !== undefined) {
|
|
304
|
+
// Refresh recency for LRU.
|
|
305
|
+
cache.delete(line);
|
|
306
|
+
cache.set(line, cached);
|
|
292
307
|
return cached;
|
|
293
308
|
}
|
|
294
309
|
try {
|
|
295
310
|
const highlighted = highlightCode(line, language)[0] ?? line;
|
|
296
|
-
|
|
297
|
-
cache.set(line, sanitized);
|
|
298
|
-
return sanitized;
|
|
311
|
+
return remember(line, sanitizeAnsiForThemedOutput(highlighted));
|
|
299
312
|
} catch {
|
|
300
|
-
|
|
301
|
-
cache.set(line, sanitizedFallback);
|
|
302
|
-
return sanitizedFallback;
|
|
313
|
+
return remember(line, sanitizeAnsiForThemedOutput(line));
|
|
303
314
|
}
|
|
304
315
|
};
|
|
305
316
|
}
|
|
@@ -1301,6 +1312,25 @@ function resolveDiffIndicatorMode(
|
|
|
1301
1312
|
return config.diffIndicatorMode ?? DEFAULT_TOOL_DISPLAY_CONFIG.diffIndicatorMode;
|
|
1302
1313
|
}
|
|
1303
1314
|
|
|
1315
|
+
/** Snapshot or live getter — panel changes must apply on the next paint. */
|
|
1316
|
+
export type DisplayConfigInput = ToolDisplayConfig | (() => ToolDisplayConfig);
|
|
1317
|
+
|
|
1318
|
+
function resolveLiveDisplayConfig(input: DisplayConfigInput): ToolDisplayConfig {
|
|
1319
|
+
return typeof input === "function" ? input() : input;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
/** Cache key fragment so indicator/wrap/limits invalidate without host recreate. */
|
|
1323
|
+
function displayConfigCacheKey(config: ToolDisplayConfig): string {
|
|
1324
|
+
return [
|
|
1325
|
+
config.diffViewMode,
|
|
1326
|
+
config.diffIndicatorMode,
|
|
1327
|
+
String(config.diffSplitMinWidth),
|
|
1328
|
+
String(config.diffCollapsedLines),
|
|
1329
|
+
config.diffWordWrap ? "1" : "0",
|
|
1330
|
+
String(config.expandedPreviewMaxLines),
|
|
1331
|
+
].join(":");
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1304
1334
|
function resolveIndicatorGlyph(
|
|
1305
1335
|
kind: DiffLineKind,
|
|
1306
1336
|
indicatorMode: DiffIndicatorMode,
|
|
@@ -2148,28 +2178,96 @@ function renderDiffSpacerLine(width: number): string {
|
|
|
2148
2178
|
return safeWidth > 0 ? " ".repeat(safeWidth) : "";
|
|
2149
2179
|
}
|
|
2150
2180
|
|
|
2151
|
-
function
|
|
2152
|
-
rows: RenderedRow[],
|
|
2153
|
-
width: number,
|
|
2181
|
+
function resolveDiffDisplayLimit(
|
|
2154
2182
|
expanded: boolean,
|
|
2155
2183
|
maxCollapsedLines: number,
|
|
2156
2184
|
maxExpandedLines: number,
|
|
2157
|
-
|
|
2158
|
-
theme: DiffTheme,
|
|
2159
|
-
): string[] {
|
|
2185
|
+
): number {
|
|
2160
2186
|
const expandedLimit = Number.isFinite(maxExpandedLines)
|
|
2161
2187
|
? maxExpandedLines
|
|
2162
2188
|
: DEFAULT_TOOL_DISPLAY_CONFIG.expandedPreviewMaxLines;
|
|
2163
2189
|
const collapsedLimit = Number.isFinite(maxCollapsedLines)
|
|
2164
2190
|
? maxCollapsedLines
|
|
2165
2191
|
: DEFAULT_TOOL_DISPLAY_CONFIG.diffCollapsedLines;
|
|
2166
|
-
|
|
2167
|
-
|
|
2192
|
+
return expanded ? Math.max(0, expandedLimit) : Math.max(1, collapsedLimit);
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
/**
|
|
2196
|
+
* How many source rows/entries to fully highlight+render before applyLineLimit.
|
|
2197
|
+
* Extra headroom covers word-wrap expansion so the display limit can still fill.
|
|
2198
|
+
*/
|
|
2199
|
+
function resolveDiffProcessBudget(displayLimit: number, wordWrap: boolean): number {
|
|
2200
|
+
if (!Number.isFinite(displayLimit) || displayLimit <= 0) {
|
|
2201
|
+
return Number.POSITIVE_INFINITY;
|
|
2202
|
+
}
|
|
2203
|
+
return wordWrap ? displayLimit * 2 + 8 : displayLimit + 4;
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
function countDiffLineEntries(entries: readonly ParsedDiffEntry[]): number {
|
|
2207
|
+
let count = 0;
|
|
2208
|
+
for (const entry of entries) {
|
|
2209
|
+
if (entry.kind === "line") count++;
|
|
2210
|
+
}
|
|
2211
|
+
return count;
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
function takeEntriesForLineBudget(
|
|
2215
|
+
entries: ParsedDiffEntry[],
|
|
2216
|
+
maxLineEntries: number,
|
|
2217
|
+
): { entries: ParsedDiffEntry[]; totalLineEntries: number; processedLineEntries: number } {
|
|
2218
|
+
const totalLineEntries = countDiffLineEntries(entries);
|
|
2219
|
+
if (!Number.isFinite(maxLineEntries) || maxLineEntries >= totalLineEntries) {
|
|
2220
|
+
return { entries, totalLineEntries, processedLineEntries: totalLineEntries };
|
|
2221
|
+
}
|
|
2222
|
+
const limited: ParsedDiffEntry[] = [];
|
|
2223
|
+
let processedLineEntries = 0;
|
|
2224
|
+
for (const entry of entries) {
|
|
2225
|
+
if (entry.kind === "line") {
|
|
2226
|
+
if (processedLineEntries >= maxLineEntries) break;
|
|
2227
|
+
processedLineEntries++;
|
|
2228
|
+
}
|
|
2229
|
+
limited.push(entry);
|
|
2230
|
+
}
|
|
2231
|
+
return { entries: limited, totalLineEntries, processedLineEntries };
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
function takeSplitRowsForBudget(
|
|
2235
|
+
rows: SplitDiffRow[],
|
|
2236
|
+
maxRows: number,
|
|
2237
|
+
): { rows: SplitDiffRow[]; totalRows: number; processedRows: number } {
|
|
2238
|
+
const totalRows = rows.length;
|
|
2239
|
+
if (!Number.isFinite(maxRows) || maxRows >= totalRows) {
|
|
2240
|
+
return { rows, totalRows, processedRows: totalRows };
|
|
2241
|
+
}
|
|
2242
|
+
const limited = rows.slice(0, maxRows);
|
|
2243
|
+
return { rows: limited, totalRows, processedRows: limited.length };
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
function applyLineLimit(
|
|
2247
|
+
rows: RenderedRow[],
|
|
2248
|
+
width: number,
|
|
2249
|
+
expanded: boolean,
|
|
2250
|
+
maxCollapsedLines: number,
|
|
2251
|
+
maxExpandedLines: number,
|
|
2252
|
+
totalHunks: number,
|
|
2253
|
+
theme: DiffTheme,
|
|
2254
|
+
/**
|
|
2255
|
+
* When the caller only rendered a prefix of the full diff, pass the unprocessed
|
|
2256
|
+
* remainder so the collapse hint still reflects total hidden content.
|
|
2257
|
+
*/
|
|
2258
|
+
unprocessedLogicalRows = 0,
|
|
2259
|
+
): string[] {
|
|
2260
|
+
const limit = resolveDiffDisplayLimit(expanded, maxCollapsedLines, maxExpandedLines);
|
|
2261
|
+
const safeUnprocessed = Math.max(0, unprocessedLogicalRows);
|
|
2262
|
+
if (limit === 0 || (rows.length <= limit && safeUnprocessed === 0)) {
|
|
2168
2263
|
return rows.map((row) => clampDiffLineToWidth(row.text, width));
|
|
2169
2264
|
}
|
|
2170
2265
|
|
|
2171
|
-
const shown = rows.slice(0, limit);
|
|
2172
|
-
const remaining = rows.length - shown.length;
|
|
2266
|
+
const shown = rows.length <= limit ? rows : rows.slice(0, limit);
|
|
2267
|
+
const remaining = Math.max(0, rows.length - shown.length) + safeUnprocessed;
|
|
2268
|
+
if (remaining === 0) {
|
|
2269
|
+
return shown.map((row) => clampDiffLineToWidth(row.text, width));
|
|
2270
|
+
}
|
|
2173
2271
|
const visibleHunks = new Set(
|
|
2174
2272
|
shown
|
|
2175
2273
|
.map((row) => row.hunkIndex)
|
|
@@ -2271,24 +2369,38 @@ function createDiffRenderCache() {
|
|
|
2271
2369
|
let cachedWidth: number | undefined;
|
|
2272
2370
|
let cachedExpanded: boolean | undefined;
|
|
2273
2371
|
let cachedMode: DiffPresentationMode | undefined;
|
|
2372
|
+
let cachedConfigKey: string | undefined;
|
|
2274
2373
|
let cachedLines: string[] | undefined;
|
|
2275
2374
|
|
|
2276
2375
|
return {
|
|
2277
|
-
get(
|
|
2376
|
+
get(
|
|
2377
|
+
width: number,
|
|
2378
|
+
expanded: boolean,
|
|
2379
|
+
mode: DiffPresentationMode,
|
|
2380
|
+
configKey: string,
|
|
2381
|
+
): string[] | undefined {
|
|
2278
2382
|
if (
|
|
2279
2383
|
cachedLines &&
|
|
2280
2384
|
cachedWidth === width &&
|
|
2281
2385
|
cachedExpanded === expanded &&
|
|
2282
|
-
cachedMode === mode
|
|
2386
|
+
cachedMode === mode &&
|
|
2387
|
+
cachedConfigKey === configKey
|
|
2283
2388
|
) {
|
|
2284
2389
|
return cachedLines;
|
|
2285
2390
|
}
|
|
2286
2391
|
return undefined;
|
|
2287
2392
|
},
|
|
2288
|
-
set(
|
|
2393
|
+
set(
|
|
2394
|
+
width: number,
|
|
2395
|
+
expanded: boolean,
|
|
2396
|
+
mode: DiffPresentationMode,
|
|
2397
|
+
configKey: string,
|
|
2398
|
+
lines: string[],
|
|
2399
|
+
): string[] {
|
|
2289
2400
|
cachedWidth = width;
|
|
2290
2401
|
cachedExpanded = expanded;
|
|
2291
2402
|
cachedMode = mode;
|
|
2403
|
+
cachedConfigKey = configKey;
|
|
2292
2404
|
cachedLines = lines;
|
|
2293
2405
|
return lines;
|
|
2294
2406
|
},
|
|
@@ -2296,6 +2408,7 @@ function createDiffRenderCache() {
|
|
|
2296
2408
|
cachedWidth = undefined;
|
|
2297
2409
|
cachedExpanded = undefined;
|
|
2298
2410
|
cachedMode = undefined;
|
|
2411
|
+
cachedConfigKey = undefined;
|
|
2299
2412
|
cachedLines = undefined;
|
|
2300
2413
|
},
|
|
2301
2414
|
};
|
|
@@ -2304,7 +2417,7 @@ function createDiffRenderCache() {
|
|
|
2304
2417
|
export function renderEditDiffResult(
|
|
2305
2418
|
details: unknown,
|
|
2306
2419
|
options: DiffRenderOptions,
|
|
2307
|
-
config:
|
|
2420
|
+
config: DisplayConfigInput,
|
|
2308
2421
|
theme: DiffTheme,
|
|
2309
2422
|
fallbackText: string,
|
|
2310
2423
|
): Component {
|
|
@@ -2339,16 +2452,19 @@ export function renderEditDiffResult(
|
|
|
2339
2452
|
const containerBgAnsi = undefined;
|
|
2340
2453
|
const language = resolveLanguageFromPath(options.filePath);
|
|
2341
2454
|
const highlightLine = createCodeLineHighlighter(language);
|
|
2342
|
-
const wordWrap = config.diffWordWrap;
|
|
2343
|
-
const indicatorMode = resolveDiffIndicatorMode(config);
|
|
2344
2455
|
|
|
2345
2456
|
const cache = createDiffRenderCache();
|
|
2346
2457
|
|
|
2347
2458
|
return {
|
|
2348
2459
|
render(width: number): string[] {
|
|
2460
|
+
// Live config: panel can change indicator/wrap/limits after this component is created.
|
|
2461
|
+
const live = resolveLiveDisplayConfig(config);
|
|
2462
|
+
const wordWrap = live.diffWordWrap;
|
|
2463
|
+
const indicatorMode = resolveDiffIndicatorMode(live);
|
|
2464
|
+
const configKey = displayConfigCacheKey(live);
|
|
2349
2465
|
const safeWidth = normalizeDiffRenderWidth(width);
|
|
2350
|
-
const mode = resolveDiffPresentationMode(
|
|
2351
|
-
const cached = cache.get(safeWidth, options.expanded, mode);
|
|
2466
|
+
const mode = resolveDiffPresentationMode(live, safeWidth, canRenderSplitLayout(safeWidth));
|
|
2467
|
+
const cached = cache.get(safeWidth, options.expanded, mode, configKey);
|
|
2352
2468
|
if (cached) {
|
|
2353
2469
|
return cached;
|
|
2354
2470
|
}
|
|
@@ -2358,6 +2474,7 @@ export function renderEditDiffResult(
|
|
|
2358
2474
|
safeWidth,
|
|
2359
2475
|
options.expanded,
|
|
2360
2476
|
mode,
|
|
2477
|
+
configKey,
|
|
2361
2478
|
clampDiffLinesToWidth(
|
|
2362
2479
|
renderSingleDiffRow(
|
|
2363
2480
|
buildDiffSummaryText(parsed.stats, safeWidth),
|
|
@@ -2371,59 +2488,47 @@ export function renderEditDiffResult(
|
|
|
2371
2488
|
}
|
|
2372
2489
|
|
|
2373
2490
|
const headerRows = renderHeaderRows(parsed.stats, mode, safeWidth, theme);
|
|
2374
|
-
const
|
|
2491
|
+
const displayLimit = resolveDiffDisplayLimit(
|
|
2492
|
+
options.expanded,
|
|
2493
|
+
live.diffCollapsedLines,
|
|
2494
|
+
live.expandedPreviewMaxLines,
|
|
2495
|
+
);
|
|
2496
|
+
const processBudget = resolveDiffProcessBudget(displayLimit, wordWrap);
|
|
2497
|
+
// Only highlight/render a prefix that can fill the display limit; full-diff
|
|
2498
|
+
// LCS + syntax highlight on thousands of hidden lines is pure waste when collapsed.
|
|
2499
|
+
const entryBudget = takeEntriesForLineBudget(parsed.entries, processBudget);
|
|
2500
|
+
const splitBudget = takeSplitRowsForBudget(splitRows, processBudget);
|
|
2501
|
+
const inlineHighlights = buildInlineHighlightMap(splitBudget.rows);
|
|
2502
|
+
const renderCtx: DiffRenderContext = {
|
|
2503
|
+
width: safeWidth,
|
|
2504
|
+
theme,
|
|
2505
|
+
inlineHighlights,
|
|
2506
|
+
palette,
|
|
2507
|
+
highlightLine,
|
|
2508
|
+
containerBgAnsi,
|
|
2509
|
+
wordWrap,
|
|
2510
|
+
indicatorMode,
|
|
2511
|
+
showHashlineAnchors,
|
|
2512
|
+
};
|
|
2375
2513
|
const bodyRows =
|
|
2376
2514
|
mode === "split"
|
|
2377
|
-
? renderSplit(
|
|
2378
|
-
splitRows,
|
|
2379
|
-
{
|
|
2380
|
-
width: safeWidth,
|
|
2381
|
-
theme,
|
|
2382
|
-
inlineHighlights,
|
|
2383
|
-
palette,
|
|
2384
|
-
highlightLine,
|
|
2385
|
-
containerBgAnsi,
|
|
2386
|
-
wordWrap,
|
|
2387
|
-
indicatorMode,
|
|
2388
|
-
showHashlineAnchors,
|
|
2389
|
-
},
|
|
2390
|
-
lineNumberWidth,
|
|
2391
|
-
)
|
|
2515
|
+
? renderSplit(splitBudget.rows, renderCtx, lineNumberWidth)
|
|
2392
2516
|
: mode === "compact"
|
|
2393
|
-
? renderCompact(
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
containerBgAnsi,
|
|
2400
|
-
wordWrap,
|
|
2401
|
-
indicatorMode,
|
|
2402
|
-
showHashlineAnchors,
|
|
2403
|
-
})
|
|
2404
|
-
: renderUnified(
|
|
2405
|
-
parsed.entries,
|
|
2406
|
-
{
|
|
2407
|
-
width: safeWidth,
|
|
2408
|
-
theme,
|
|
2409
|
-
inlineHighlights,
|
|
2410
|
-
palette,
|
|
2411
|
-
highlightLine,
|
|
2412
|
-
containerBgAnsi,
|
|
2413
|
-
wordWrap,
|
|
2414
|
-
indicatorMode,
|
|
2415
|
-
showHashlineAnchors,
|
|
2416
|
-
},
|
|
2417
|
-
lineNumberWidth,
|
|
2418
|
-
);
|
|
2517
|
+
? renderCompact(entryBudget.entries, renderCtx)
|
|
2518
|
+
: renderUnified(entryBudget.entries, renderCtx, lineNumberWidth);
|
|
2519
|
+
const unprocessedLogicalRows =
|
|
2520
|
+
mode === "split"
|
|
2521
|
+
? Math.max(0, splitBudget.totalRows - splitBudget.processedRows)
|
|
2522
|
+
: Math.max(0, entryBudget.totalLineEntries - entryBudget.processedLineEntries);
|
|
2419
2523
|
const bodyWithLimit = applyLineLimit(
|
|
2420
2524
|
bodyRows,
|
|
2421
2525
|
safeWidth,
|
|
2422
2526
|
options.expanded,
|
|
2423
|
-
|
|
2424
|
-
|
|
2527
|
+
live.diffCollapsedLines,
|
|
2528
|
+
live.expandedPreviewMaxLines,
|
|
2425
2529
|
parsed.stats.hunks,
|
|
2426
2530
|
theme,
|
|
2531
|
+
unprocessedLogicalRows,
|
|
2427
2532
|
);
|
|
2428
2533
|
const frame = renderDiffFrameLine(safeWidth, theme);
|
|
2429
2534
|
const renderedLines =
|
|
@@ -2432,7 +2537,7 @@ export function renderEditDiffResult(
|
|
|
2432
2537
|
: [...headerRows.map((row) => row.text), ...bodyWithLimit];
|
|
2433
2538
|
|
|
2434
2539
|
const clampedLines = clampDiffLinesToWidth(renderedLines, safeWidth);
|
|
2435
|
-
return cache.set(safeWidth, options.expanded, mode, clampedLines);
|
|
2540
|
+
return cache.set(safeWidth, options.expanded, mode, configKey, clampedLines);
|
|
2436
2541
|
},
|
|
2437
2542
|
invalidate: cache.invalidate,
|
|
2438
2543
|
};
|
|
@@ -2593,6 +2698,8 @@ interface WriteOverwriteGuard {
|
|
|
2593
2698
|
|
|
2594
2699
|
const MAX_WRITE_OVERWRITE_DIFF_LINES = 4000;
|
|
2595
2700
|
const MAX_WRITE_OVERWRITE_DIFF_MATRIX_CELLS = 1_000_000;
|
|
2701
|
+
/** Collapsed overwrite previews refuse larger LCS matrices to avoid TUI freezes. */
|
|
2702
|
+
const MAX_COLLAPSED_WRITE_OVERWRITE_DIFF_MATRIX_CELLS = 200_000;
|
|
2596
2703
|
|
|
2597
2704
|
function buildApproximateWriteStats(
|
|
2598
2705
|
lineCount: number,
|
|
@@ -2631,6 +2738,7 @@ function buildWriteDiffData(entries: ParsedDiffEntry[]): WriteDiffData {
|
|
|
2631
2738
|
function resolveWriteOverwriteGuard(
|
|
2632
2739
|
previousLines: string[],
|
|
2633
2740
|
nextLines: string[],
|
|
2741
|
+
expanded = true,
|
|
2634
2742
|
): WriteOverwriteGuard | undefined {
|
|
2635
2743
|
const previousLineCount = previousLines.length;
|
|
2636
2744
|
const nextLineCount = nextLines.length;
|
|
@@ -2643,7 +2751,10 @@ function resolveWriteOverwriteGuard(
|
|
|
2643
2751
|
if (previousLineCount === 0 || nextLineCount === 0) {
|
|
2644
2752
|
return undefined;
|
|
2645
2753
|
}
|
|
2646
|
-
|
|
2754
|
+
const cellLimit = expanded
|
|
2755
|
+
? MAX_WRITE_OVERWRITE_DIFF_MATRIX_CELLS
|
|
2756
|
+
: MAX_COLLAPSED_WRITE_OVERWRITE_DIFF_MATRIX_CELLS;
|
|
2757
|
+
return previousLineCount * nextLineCount > cellLimit
|
|
2647
2758
|
? { previousLineCount, nextLineCount }
|
|
2648
2759
|
: undefined;
|
|
2649
2760
|
}
|
|
@@ -2680,7 +2791,7 @@ function renderWriteOverwriteGuardRows(
|
|
|
2680
2791
|
export function renderWriteDiffResult(
|
|
2681
2792
|
content: string | undefined,
|
|
2682
2793
|
options: DiffRenderOptions,
|
|
2683
|
-
config:
|
|
2794
|
+
config: DisplayConfigInput,
|
|
2684
2795
|
theme: DiffTheme,
|
|
2685
2796
|
fallbackText: string,
|
|
2686
2797
|
): Component {
|
|
@@ -2704,16 +2815,11 @@ export function renderWriteDiffResult(
|
|
|
2704
2815
|
previousLines.length,
|
|
2705
2816
|
hasComparablePrevious,
|
|
2706
2817
|
);
|
|
2707
|
-
const overwriteGuard = hasComparablePrevious
|
|
2708
|
-
? resolveWriteOverwriteGuard(previousLines, lines)
|
|
2709
|
-
: undefined;
|
|
2710
2818
|
const palette = resolveDiffPalette(theme);
|
|
2711
2819
|
// Keep the panel transparent; changed rows and inline spans retain their own highlights.
|
|
2712
2820
|
const containerBgAnsi = undefined;
|
|
2713
2821
|
const language = resolveLanguageFromPath(filePath);
|
|
2714
2822
|
const highlightLine = createCodeLineHighlighter(language);
|
|
2715
|
-
const wordWrap = config.diffWordWrap;
|
|
2716
|
-
const indicatorMode = resolveDiffIndicatorMode(config);
|
|
2717
2823
|
|
|
2718
2824
|
let detailedData: WriteDiffData | undefined;
|
|
2719
2825
|
const cache = createDiffRenderCache();
|
|
@@ -2731,9 +2837,14 @@ export function renderWriteDiffResult(
|
|
|
2731
2837
|
|
|
2732
2838
|
return {
|
|
2733
2839
|
render(width: number): string[] {
|
|
2840
|
+
// Live config: panel can change indicator/wrap/limits after this component is created.
|
|
2841
|
+
const live = resolveLiveDisplayConfig(config);
|
|
2842
|
+
const wordWrap = live.diffWordWrap;
|
|
2843
|
+
const indicatorMode = resolveDiffIndicatorMode(live);
|
|
2844
|
+
const configKey = displayConfigCacheKey(live);
|
|
2734
2845
|
const safeWidth = normalizeDiffRenderWidth(width);
|
|
2735
2846
|
const resolvedMode = resolveDiffPresentationMode(
|
|
2736
|
-
|
|
2847
|
+
live,
|
|
2737
2848
|
safeWidth,
|
|
2738
2849
|
canRenderSplitLayout(safeWidth),
|
|
2739
2850
|
);
|
|
@@ -2742,7 +2853,7 @@ export function renderWriteDiffResult(
|
|
|
2742
2853
|
: resolvedMode === "split"
|
|
2743
2854
|
? "unified"
|
|
2744
2855
|
: resolvedMode;
|
|
2745
|
-
const cached = cache.get(safeWidth, options.expanded, mode);
|
|
2856
|
+
const cached = cache.get(safeWidth, options.expanded, mode, configKey);
|
|
2746
2857
|
if (cached) {
|
|
2747
2858
|
return cached;
|
|
2748
2859
|
}
|
|
@@ -2753,11 +2864,16 @@ export function renderWriteDiffResult(
|
|
|
2753
2864
|
theme,
|
|
2754
2865
|
options.headerLabel,
|
|
2755
2866
|
);
|
|
2867
|
+
// Guard depends on expanded: collapsed previews use a stricter LCS cell budget.
|
|
2868
|
+
const overwriteGuard = hasComparablePrevious
|
|
2869
|
+
? resolveWriteOverwriteGuard(previousLines, lines, options.expanded)
|
|
2870
|
+
: undefined;
|
|
2756
2871
|
if (overwriteGuard) {
|
|
2757
2872
|
return cache.set(
|
|
2758
2873
|
safeWidth,
|
|
2759
2874
|
options.expanded,
|
|
2760
2875
|
mode,
|
|
2876
|
+
configKey,
|
|
2761
2877
|
clampDiffLinesToWidth(
|
|
2762
2878
|
[header, ...renderWriteOverwriteGuardRows(overwriteGuard, safeWidth, theme)],
|
|
2763
2879
|
safeWidth,
|
|
@@ -2782,15 +2898,28 @@ export function renderWriteDiffResult(
|
|
|
2782
2898
|
safeWidth,
|
|
2783
2899
|
options.expanded,
|
|
2784
2900
|
mode,
|
|
2901
|
+
configKey,
|
|
2785
2902
|
clampDiffLinesToWidth(summaryRows, safeWidth),
|
|
2786
2903
|
);
|
|
2787
2904
|
}
|
|
2788
2905
|
|
|
2789
2906
|
const data = getDetailedData();
|
|
2907
|
+
const displayLimit = resolveDiffDisplayLimit(
|
|
2908
|
+
options.expanded,
|
|
2909
|
+
live.diffCollapsedLines,
|
|
2910
|
+
live.expandedPreviewMaxLines,
|
|
2911
|
+
);
|
|
2912
|
+
const processBudget = resolveDiffProcessBudget(displayLimit, wordWrap);
|
|
2913
|
+
const entryBudget = takeEntriesForLineBudget(data.entries, processBudget);
|
|
2914
|
+
const splitBudget = takeSplitRowsForBudget(data.splitRows, processBudget);
|
|
2915
|
+
const inlineHighlights =
|
|
2916
|
+
splitBudget.processedRows < splitBudget.totalRows
|
|
2917
|
+
? buildInlineHighlightMap(splitBudget.rows)
|
|
2918
|
+
: data.inlineHighlights;
|
|
2790
2919
|
const renderCtx: DiffRenderContext = {
|
|
2791
2920
|
width: safeWidth,
|
|
2792
2921
|
theme,
|
|
2793
|
-
inlineHighlights
|
|
2922
|
+
inlineHighlights,
|
|
2794
2923
|
palette,
|
|
2795
2924
|
highlightLine,
|
|
2796
2925
|
containerBgAnsi,
|
|
@@ -2799,28 +2928,33 @@ export function renderWriteDiffResult(
|
|
|
2799
2928
|
showHashlineAnchors: false,
|
|
2800
2929
|
};
|
|
2801
2930
|
const bodyRows: RenderedRow[] =
|
|
2802
|
-
|
|
2931
|
+
entryBudget.entries.length === 0
|
|
2803
2932
|
? [{ text: theme.fg("muted", "(empty file)"), hunkIndex: null }]
|
|
2804
2933
|
: mode === "split"
|
|
2805
|
-
? renderSplit(
|
|
2934
|
+
? renderSplit(splitBudget.rows, renderCtx, data.lineNumberWidth)
|
|
2806
2935
|
: mode === "compact"
|
|
2807
|
-
? renderCompact(
|
|
2808
|
-
: renderUnified(
|
|
2936
|
+
? renderCompact(entryBudget.entries, renderCtx)
|
|
2937
|
+
: renderUnified(entryBudget.entries, renderCtx, data.lineNumberWidth);
|
|
2938
|
+
const unprocessedLogicalRows =
|
|
2939
|
+
mode === "split"
|
|
2940
|
+
? Math.max(0, splitBudget.totalRows - splitBudget.processedRows)
|
|
2941
|
+
: Math.max(0, entryBudget.totalLineEntries - entryBudget.processedLineEntries);
|
|
2809
2942
|
|
|
2810
2943
|
const bodyWithLimit = applyLineLimit(
|
|
2811
2944
|
bodyRows,
|
|
2812
2945
|
safeWidth,
|
|
2813
2946
|
options.expanded,
|
|
2814
|
-
|
|
2815
|
-
|
|
2947
|
+
live.diffCollapsedLines,
|
|
2948
|
+
live.expandedPreviewMaxLines,
|
|
2816
2949
|
data.hunkCount,
|
|
2817
2950
|
theme,
|
|
2951
|
+
unprocessedLogicalRows,
|
|
2818
2952
|
);
|
|
2819
2953
|
const frame = renderDiffFrameLine(safeWidth, theme);
|
|
2820
2954
|
const renderedLines =
|
|
2821
2955
|
mode === "unified" ? [header, frame, ...bodyWithLimit, frame] : [header, ...bodyWithLimit];
|
|
2822
2956
|
const finalLines = clampDiffLinesToWidth(renderedLines, safeWidth);
|
|
2823
|
-
return cache.set(safeWidth, options.expanded, mode, finalLines);
|
|
2957
|
+
return cache.set(safeWidth, options.expanded, mode, configKey, finalLines);
|
|
2824
2958
|
},
|
|
2825
2959
|
invalidate: cache.invalidate,
|
|
2826
2960
|
};
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { createWriteToolDefinition, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
renderEditDiffResult,
|
|
5
|
+
renderWriteDiffResult,
|
|
6
|
+
type DisplayConfigInput,
|
|
7
|
+
} from "./diff-renderer.ts";
|
|
4
8
|
import { DEFAULT_TOOL_DISPLAY_CONFIG } from "./types.ts";
|
|
5
9
|
import { executeWriteWithMetadata, WriteExecutionMetadataStore } from "./write-execution.ts";
|
|
6
10
|
|
|
@@ -34,6 +38,8 @@ export function renderRichToolResult(
|
|
|
34
38
|
theme: any,
|
|
35
39
|
context: any,
|
|
36
40
|
writeMetadata: WriteExecutionMetadataStore,
|
|
41
|
+
/** Plain snapshot or live getter — getter lets /ccstyle panel changes repaint existing diffs. */
|
|
42
|
+
displayConfig: DisplayConfigInput = DEFAULT_TOOL_DISPLAY_CONFIG,
|
|
37
43
|
): any | undefined {
|
|
38
44
|
if (options?.isPartial || options?.isError || context?.isError) return undefined;
|
|
39
45
|
const expanded = options?.expanded === true || context?.expanded === true;
|
|
@@ -42,7 +48,7 @@ export function renderRichToolResult(
|
|
|
42
48
|
return renderEditDiffResult(
|
|
43
49
|
result?.details,
|
|
44
50
|
{ expanded, filePath },
|
|
45
|
-
|
|
51
|
+
displayConfig,
|
|
46
52
|
theme,
|
|
47
53
|
resultText(result),
|
|
48
54
|
);
|
|
@@ -64,7 +70,7 @@ export function renderRichToolResult(
|
|
|
64
70
|
previousContent: metadata.previousContent,
|
|
65
71
|
fileExistedBeforeWrite: metadata.fileExistedBeforeWrite,
|
|
66
72
|
},
|
|
67
|
-
|
|
73
|
+
displayConfig,
|
|
68
74
|
theme,
|
|
69
75
|
resultText(result),
|
|
70
76
|
);
|
|
@@ -103,4 +109,11 @@ export function installWriteOverride(
|
|
|
103
109
|
return store;
|
|
104
110
|
}
|
|
105
111
|
|
|
106
|
-
export {
|
|
112
|
+
export {
|
|
113
|
+
DEFAULT_TOOL_DISPLAY_CONFIG,
|
|
114
|
+
type ToolDisplayConfig,
|
|
115
|
+
type DiffViewMode,
|
|
116
|
+
type DiffIndicatorMode,
|
|
117
|
+
} from "./types.ts";
|
|
118
|
+
export type { DisplayConfigInput } from "./diff-renderer.ts";
|
|
119
|
+
export { WriteExecutionMetadataStore } from "./write-execution.ts";
|
|
@@ -14,7 +14,13 @@ export const DEFAULT_TOOL_DISPLAY_CONFIG: ToolDisplayConfig = {
|
|
|
14
14
|
diffViewMode: "auto",
|
|
15
15
|
diffIndicatorMode: "bars",
|
|
16
16
|
diffSplitMinWidth: 120,
|
|
17
|
+
/** Collapsed tool/diff body: ~half a typical terminal after chrome. */
|
|
17
18
|
diffCollapsedLines: 24,
|
|
18
19
|
diffWordWrap: true,
|
|
19
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Expanded tool/diff body cap. 40 ≈ one screen of content after title,
|
|
22
|
+
* Input section, editor, and status — keeps the TUI compact.
|
|
23
|
+
* Raise via /ccstyle → Diff → Expanded max lines when reviewing large dumps.
|
|
24
|
+
*/
|
|
25
|
+
expandedPreviewMaxLines: 40,
|
|
20
26
|
};
|
|
@@ -6,9 +6,16 @@ const UNSAFE_TERMINAL_ESCAPE = new RegExp(
|
|
|
6
6
|
"g",
|
|
7
7
|
);
|
|
8
8
|
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Prevent captured terminal control responses from being replayed by tool renderers.
|
|
11
|
+
* Optional `maxChars` trims the input first so preview/one-line paths never scan multi-MB outputs.
|
|
12
|
+
*/
|
|
13
|
+
export function sanitizeToolResultText(value: string, maxChars?: number): string {
|
|
14
|
+
const source =
|
|
15
|
+
typeof maxChars === "number" && maxChars >= 0 && value.length > maxChars
|
|
16
|
+
? value.slice(0, maxChars)
|
|
17
|
+
: value;
|
|
18
|
+
return source
|
|
12
19
|
.replace(UNSAFE_TERMINAL_ESCAPE, "")
|
|
13
20
|
.replace(/\x1B/g, "")
|
|
14
21
|
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g, "")
|
package/package.json
CHANGED