pi-cc-extensions 0.8.61 → 0.8.63
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/README.en.md +4 -2
- package/README.md +4 -2
- package/extensions/config/config.ts +10 -10
- package/extensions/config/panel.ts +10 -5
- package/extensions/feature/compact-thinking.ts +73 -12
- package/extensions/feature/shell/model-status.ts +19 -0
- package/extensions/index.ts +2 -0
- package/extensions/renderer/compact-mode.ts +138 -97
- package/extensions/renderer/mouse/interaction.ts +46 -16
- package/extensions/renderer/mouse/layout.ts +36 -2
- package/extensions/renderer/tool/diff/diff-component.ts +1 -1
- package/extensions/renderer/tool/diff/diff-edit-render.ts +2 -2
- package/extensions/renderer/tool/diff/diff-limits.ts +5 -5
- package/extensions/renderer/tool/grouping.ts +6 -0
- package/extensions/renderer/tool/message-display.ts +1 -1
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -39,7 +39,7 @@ Run `/reload` after installation.
|
|
|
39
39
|
| --------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------- |
|
|
40
40
|
| Claude Code Output | Tool summaries, expand/collapse, rich edit/write diffs, and`on` / `compact` / `off` modes | `/ccstyle` |
|
|
41
41
|
| Markdown enhancements | Mermaid art, admonitions, URL linking, and more | Automatic |
|
|
42
|
-
| Fullscreen mode | Tool card/group click-to
|
|
42
|
+
| Fullscreen mode | Tool card/group click to expand, double-click to collapse, previews, hover highlight, and a back-to-bottom button | `TUIMODE=fullscreen` or `--tui-mode fullscreen` |
|
|
43
43
|
| Settings panel | `Style / Diff / Thinking / Feature` tabs: startup header toggle and wheel step | `/ccstyle` |
|
|
44
44
|
| Context inspection | Usage breakdown and previews for the system prompt, tools, skills, and messages | `/context` |
|
|
45
45
|
| Session/Subagent references | Search and inject effective context from previous Sessions or existing SubAgents | `@` |
|
|
@@ -56,7 +56,7 @@ Run `/reload` after installation.
|
|
|
56
56
|
"diffViewMode": "auto", // diff layout: auto / split / unified
|
|
57
57
|
"diffIndicatorMode": "bars", // diff change indicators: bars / classic / none
|
|
58
58
|
"diffSplitMinWidth": 120, // min terminal width before auto layout uses side-by-side columns
|
|
59
|
-
"
|
|
59
|
+
"editDiffCollapsedLines": 24, // Edit collapsed lines; beyond that shows the expand hint (Ctrl+O / click)
|
|
60
60
|
"writeDiffCollapsedLines": 0, // write collapsed body lines; 0 = ↳ created • expand hint (stats stay on the title)
|
|
61
61
|
"diffWordWrap": true, // whether long diff lines wrap (otherwise truncated)
|
|
62
62
|
"expandedPreviewMaxLines": 40, // max body lines for expanded output/diff
|
|
@@ -68,6 +68,8 @@ Run `/reload` after installation.
|
|
|
68
68
|
}
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
> **Fullscreen clicks**: click `click to show more` to expand tool cards, thinking, Skill, and compact summaries. Click a truncated Input/Output header to open the full preview. Double-click an expanded panel to collapse it. Single clicks on an expanded card still select text and open links.
|
|
72
|
+
>
|
|
71
73
|
> **Tip**: set `markdown.mermaid` to `final` via `~/.pi/agent/settings.json` or the Mermaid diagrams option in `/settings`. Default `streaming` redraws per frame; `final` renders once at completion.
|
|
72
74
|
|
|
73
75
|
## Local development
|
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ pi install git:github.com/minuque/pi-cc-extensions
|
|
|
39
39
|
| --------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------- |
|
|
40
40
|
| Claude Code UI | 工具摘要、折叠展开、rich edit/write diff,以及`on` / `compact` / `off` 三种模式 | `/ccstyle` |
|
|
41
41
|
| Markdown 增强 | Mermaid 图、提示框、URL 链接化等 | 自动生效 |
|
|
42
|
-
| Fullscreen mode | 工具卡/group
|
|
42
|
+
| Fullscreen mode | 工具卡/group 单击展开、双击收起、预览、hover 高亮、回到底部按钮 | `TUIMODE=fullscreen` 或 `--tui-mode fullscreen` |
|
|
43
43
|
| 配置面板 | `Style / Diff / Thinking / Feature` 四页签,含启动头开关与滚轮步进 | `/ccstyle` |
|
|
44
44
|
| 上下文检查 | 查看上下文占用,并预览 System prompt、Tools、Skills 和消息内容 | `/context` |
|
|
45
45
|
| Session/Subagent 引用 | 搜索并注入历史 Session 或现有 SubAgent 的有效上下文 | `@` |
|
|
@@ -56,7 +56,7 @@ pi install git:github.com/minuque/pi-cc-extensions
|
|
|
56
56
|
"diffViewMode": "auto", // diff 布局:auto / split / unified
|
|
57
57
|
"diffIndicatorMode": "bars", // diff 变更指示:bars / classic / none
|
|
58
58
|
"diffSplitMinWidth": 120, // 自动布局下使用左右分栏的最小终端宽度
|
|
59
|
-
"
|
|
59
|
+
"editDiffCollapsedLines": 24, // Edit 折叠行数;超出显示展开提示(Ctrl+O / 点击)
|
|
60
60
|
"writeDiffCollapsedLines": 0, // write 折叠行数;0 = ↳ created • 展开提示(统计在标题)
|
|
61
61
|
"diffWordWrap": true, // 长 diff 行是否换行(否则截断)
|
|
62
62
|
"expandedPreviewMaxLines": 40, // 展开后输出/diff 正文的最大行数
|
|
@@ -74,6 +74,8 @@ pi install git:github.com/minuque/pi-cc-extensions
|
|
|
74
74
|
}
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
> **Fullscreen 点击**:单击 `click to show more` 展开工具卡、思考、Skill 和 compact 摘要。单击截断的 Input/Output 打开全量预览。双击展开面板收起。展开卡上的单击仍可选择文本、打开链接。
|
|
78
|
+
>
|
|
77
79
|
> **建议**:`markdown.mermaid` 设为 `final`(`~/.pi/agent/settings.json` 或 `/settings` 面板的 Mermaid diagrams 选项)。默认 `streaming` 逐帧重绘,`final` 渲染最终版更稳定。
|
|
78
80
|
|
|
79
81
|
## 本地开发
|
|
@@ -15,7 +15,7 @@ export interface ToolDisplayConfig {
|
|
|
15
15
|
diffViewMode: DiffViewMode;
|
|
16
16
|
diffIndicatorMode: DiffIndicatorMode;
|
|
17
17
|
diffSplitMinWidth: number;
|
|
18
|
-
|
|
18
|
+
editDiffCollapsedLines: number;
|
|
19
19
|
/** Write-only collapsed body lines. 0 = `↳ created • click to show more`. */
|
|
20
20
|
writeDiffCollapsedLines: number;
|
|
21
21
|
diffWordWrap: boolean;
|
|
@@ -26,8 +26,8 @@ export const DEFAULT_TOOL_DISPLAY_CONFIG: ToolDisplayConfig = {
|
|
|
26
26
|
diffViewMode: "auto",
|
|
27
27
|
diffIndicatorMode: "bars",
|
|
28
28
|
diffSplitMinWidth: 120,
|
|
29
|
-
/** Collapsed
|
|
30
|
-
|
|
29
|
+
/** Collapsed edit/diff body: ~half a typical terminal after chrome. */
|
|
30
|
+
editDiffCollapsedLines: 24,
|
|
31
31
|
/**
|
|
32
32
|
* Write create/overwrite collapsed body.
|
|
33
33
|
* 0 = `↳ created • click to show more` (stats stay on the title).
|
|
@@ -48,7 +48,7 @@ export type Config = {
|
|
|
48
48
|
diffViewMode: DiffViewMode;
|
|
49
49
|
diffIndicatorMode: DiffIndicatorMode;
|
|
50
50
|
diffSplitMinWidth: number;
|
|
51
|
-
|
|
51
|
+
editDiffCollapsedLines: number;
|
|
52
52
|
writeDiffCollapsedLines: number;
|
|
53
53
|
diffWordWrap: boolean;
|
|
54
54
|
expandedPreviewMaxLines: number;
|
|
@@ -99,7 +99,7 @@ export const DEFAULT_CONFIG: Config = {
|
|
|
99
99
|
diffViewMode: DEFAULT_TOOL_DISPLAY_CONFIG.diffViewMode,
|
|
100
100
|
diffIndicatorMode: DEFAULT_TOOL_DISPLAY_CONFIG.diffIndicatorMode,
|
|
101
101
|
diffSplitMinWidth: DEFAULT_TOOL_DISPLAY_CONFIG.diffSplitMinWidth,
|
|
102
|
-
|
|
102
|
+
editDiffCollapsedLines: DEFAULT_TOOL_DISPLAY_CONFIG.editDiffCollapsedLines,
|
|
103
103
|
writeDiffCollapsedLines: DEFAULT_TOOL_DISPLAY_CONFIG.writeDiffCollapsedLines,
|
|
104
104
|
diffWordWrap: DEFAULT_TOOL_DISPLAY_CONFIG.diffWordWrap,
|
|
105
105
|
expandedPreviewMaxLines: DEFAULT_TOOL_DISPLAY_CONFIG.expandedPreviewMaxLines,
|
|
@@ -169,9 +169,9 @@ export function normalizeConfig(input: unknown): Config {
|
|
|
169
169
|
40,
|
|
170
170
|
300,
|
|
171
171
|
),
|
|
172
|
-
|
|
173
|
-
source.diffCollapsedLines,
|
|
174
|
-
DEFAULT_CONFIG.
|
|
172
|
+
editDiffCollapsedLines: pickPositiveInt(
|
|
173
|
+
source.editDiffCollapsedLines ?? source.diffCollapsedLines,
|
|
174
|
+
DEFAULT_CONFIG.editDiffCollapsedLines,
|
|
175
175
|
1,
|
|
176
176
|
500,
|
|
177
177
|
),
|
|
@@ -223,7 +223,7 @@ export function getToolDisplayConfig(source: Config = config): ToolDisplayConfig
|
|
|
223
223
|
diffViewMode: source.diffViewMode,
|
|
224
224
|
diffIndicatorMode: source.diffIndicatorMode,
|
|
225
225
|
diffSplitMinWidth: source.diffSplitMinWidth,
|
|
226
|
-
|
|
226
|
+
editDiffCollapsedLines: source.editDiffCollapsedLines,
|
|
227
227
|
writeDiffCollapsedLines: source.writeDiffCollapsedLines,
|
|
228
228
|
diffWordWrap: source.diffWordWrap,
|
|
229
229
|
expandedPreviewMaxLines: source.expandedPreviewMaxLines,
|
|
@@ -241,7 +241,7 @@ export function formatConfigStatus(source: Config = config): string {
|
|
|
241
241
|
`diffView=${source.diffViewMode}`,
|
|
242
242
|
`diffIndicator=${source.diffIndicatorMode}`,
|
|
243
243
|
`diffSplitMin=${source.diffSplitMinWidth}`,
|
|
244
|
-
`
|
|
244
|
+
`editCollapsed=${source.editDiffCollapsedLines}`,
|
|
245
245
|
`writeCollapsed=${source.writeDiffCollapsedLines}`,
|
|
246
246
|
`diffWordWrap=${source.diffWordWrap ? "on" : "off"}`,
|
|
247
247
|
`expandedMax=${source.expandedPreviewMaxLines}`,
|
|
@@ -288,11 +288,11 @@ export async function showCcstylePanel(
|
|
|
288
288
|
buildNumberInputSubmenu(theme, diffSplitSetting, closeSubmenu),
|
|
289
289
|
};
|
|
290
290
|
const diffCollapsedSetting = {
|
|
291
|
-
id: "
|
|
291
|
+
id: "editDiffCollapsedLines",
|
|
292
292
|
label: "Edit collapsed lines",
|
|
293
293
|
description:
|
|
294
294
|
"How many edit/diff body lines to show before the expand hint (Ctrl+O / click). Write uses its own setting below.",
|
|
295
|
-
currentValue: String(config.
|
|
295
|
+
currentValue: String(config.editDiffCollapsedLines),
|
|
296
296
|
values: [...DIFF_COLLAPSED_LINES_VALUES],
|
|
297
297
|
submenu: (_current: string, closeSubmenu: (selected?: string) => void) =>
|
|
298
298
|
buildNumberInputSubmenu(theme, diffCollapsedSetting, closeSubmenu),
|
|
@@ -458,11 +458,16 @@ export async function showCcstylePanel(
|
|
|
458
458
|
});
|
|
459
459
|
diffSplitSetting.currentValue = String(config.diffSplitMinWidth);
|
|
460
460
|
break;
|
|
461
|
-
case "
|
|
461
|
+
case "editDiffCollapsedLines":
|
|
462
462
|
updateConfig({
|
|
463
|
-
|
|
463
|
+
editDiffCollapsedLines: pickPositiveInt(
|
|
464
|
+
value,
|
|
465
|
+
DEFAULT_CONFIG.editDiffCollapsedLines,
|
|
466
|
+
1,
|
|
467
|
+
500,
|
|
468
|
+
),
|
|
464
469
|
});
|
|
465
|
-
diffCollapsedSetting.currentValue = String(config.
|
|
470
|
+
diffCollapsedSetting.currentValue = String(config.editDiffCollapsedLines);
|
|
466
471
|
break;
|
|
467
472
|
case "writeDiffCollapsedLines":
|
|
468
473
|
updateConfig({
|
|
@@ -174,11 +174,38 @@ function thinkingExpandAction(): string | undefined {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
// 只 wrap 尾部窗口;缓存未着色折行,着色放到取出时做,避免主题切换命中旧 ANSI。
|
|
177
|
-
|
|
178
|
-
const
|
|
177
|
+
type PreviewCacheEntry = { lines: string[]; hiddenBefore: number };
|
|
178
|
+
const previewCache = new Map<string, PreviewCacheEntry>();
|
|
179
|
+
export const THINKING_PREVIEW_CACHE_MAX = 2_048;
|
|
179
180
|
const PREVIEW_BUDGET_MIN_CHARS = 2_000;
|
|
180
181
|
const PREVIEW_BUDGET_SLACK_LINES = 2;
|
|
181
182
|
|
|
183
|
+
function getCachedPreview(key: string): PreviewCacheEntry | undefined {
|
|
184
|
+
const entry = previewCache.get(key);
|
|
185
|
+
if (!entry) return undefined;
|
|
186
|
+
previewCache.delete(key);
|
|
187
|
+
previewCache.set(key, entry);
|
|
188
|
+
return entry;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function cachePreview(key: string, entry: PreviewCacheEntry): void {
|
|
192
|
+
previewCache.delete(key);
|
|
193
|
+
while (previewCache.size >= THINKING_PREVIEW_CACHE_MAX) {
|
|
194
|
+
const oldest = previewCache.keys().next().value;
|
|
195
|
+
if (oldest === undefined) break;
|
|
196
|
+
previewCache.delete(oldest);
|
|
197
|
+
}
|
|
198
|
+
previewCache.set(key, entry);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function clearThinkingPreviewCache(): void {
|
|
202
|
+
previewCache.clear();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function thinkingPreviewCacheSize(): number {
|
|
206
|
+
return previewCache.size;
|
|
207
|
+
}
|
|
208
|
+
|
|
182
209
|
/** 按可见宽度估算折行数,不走 Text 全量 wrap。无换行长段也能计到隐藏行。 */
|
|
183
210
|
function countWrappedLines(text: string, contentWidth: number): number {
|
|
184
211
|
if (!text) return 0;
|
|
@@ -219,14 +246,13 @@ function layoutThinkingPreview(
|
|
|
219
246
|
else cut = 0;
|
|
220
247
|
}
|
|
221
248
|
const cacheKey = `${width}:${padding}:${cut}:${source}`;
|
|
222
|
-
let entry =
|
|
249
|
+
let entry = getCachedPreview(cacheKey);
|
|
223
250
|
if (!entry) {
|
|
224
251
|
entry = {
|
|
225
252
|
lines: wrapTextWithAnsi(source.replace(/\t/g, " "), contentWidth),
|
|
226
253
|
hiddenBefore: cut > 0 ? countWrappedLines(text.slice(0, cut), contentWidth) : 0,
|
|
227
254
|
};
|
|
228
|
-
|
|
229
|
-
previewCache.set(cacheKey, entry);
|
|
255
|
+
cachePreview(cacheKey, entry);
|
|
230
256
|
}
|
|
231
257
|
const hiddenLines = entry.hiddenBefore + Math.max(0, entry.lines.length - config.previewLines);
|
|
232
258
|
const visible = hiddenLines > 0 ? entry.lines.slice(-config.previewLines) : entry.lines;
|
|
@@ -254,7 +280,7 @@ function hiddenPreviewHint(
|
|
|
254
280
|
|
|
255
281
|
const expandedThinking = new Set<number>();
|
|
256
282
|
|
|
257
|
-
/** 折叠预览 + 展开全文。fullscreen 点击 hint
|
|
283
|
+
/** 折叠预览 + 展开全文。fullscreen 点击 hint 展开、双击整块收起,对齐工具卡。 */
|
|
258
284
|
export class ThinkingPreviewBlock implements Component {
|
|
259
285
|
private heading: string;
|
|
260
286
|
private text: string;
|
|
@@ -265,6 +291,15 @@ export class ThinkingPreviewBlock implements Component {
|
|
|
265
291
|
private _expanded: boolean;
|
|
266
292
|
private hintHovered = false;
|
|
267
293
|
private expandedBody: { width: number; lines: string[] } | undefined;
|
|
294
|
+
private collapsedMemo:
|
|
295
|
+
| {
|
|
296
|
+
width: number;
|
|
297
|
+
previewLines: number;
|
|
298
|
+
hintHovered: boolean;
|
|
299
|
+
expandAction: string | undefined;
|
|
300
|
+
lines: string[];
|
|
301
|
+
}
|
|
302
|
+
| undefined;
|
|
268
303
|
|
|
269
304
|
constructor(
|
|
270
305
|
heading: string,
|
|
@@ -294,6 +329,7 @@ export class ThinkingPreviewBlock implements Component {
|
|
|
294
329
|
}
|
|
295
330
|
|
|
296
331
|
setExpanded(expanded: boolean): void {
|
|
332
|
+
if (this._expanded !== expanded) this.collapsedMemo = undefined;
|
|
297
333
|
this._expanded = expanded;
|
|
298
334
|
if (expanded) expandedThinking.add(this.messageTimestamp);
|
|
299
335
|
else {
|
|
@@ -303,6 +339,7 @@ export class ThinkingPreviewBlock implements Component {
|
|
|
303
339
|
}
|
|
304
340
|
|
|
305
341
|
setHintHovered(hovered: boolean): void {
|
|
342
|
+
if (this.hintHovered !== hovered) this.collapsedMemo = undefined;
|
|
306
343
|
this.hintHovered = hovered;
|
|
307
344
|
}
|
|
308
345
|
|
|
@@ -362,16 +399,39 @@ export class ThinkingPreviewBlock implements Component {
|
|
|
362
399
|
});
|
|
363
400
|
return box.render(width);
|
|
364
401
|
}
|
|
402
|
+
|
|
403
|
+
const expandAction = thinkingExpandAction();
|
|
404
|
+
if (
|
|
405
|
+
this.collapsedMemo?.width === width &&
|
|
406
|
+
this.collapsedMemo.previewLines === config.previewLines &&
|
|
407
|
+
this.collapsedMemo.hintHovered === this.hintHovered &&
|
|
408
|
+
this.collapsedMemo.expandAction === expandAction
|
|
409
|
+
) {
|
|
410
|
+
return this.collapsedMemo.lines;
|
|
411
|
+
}
|
|
412
|
+
|
|
365
413
|
const body = this.bodyLines(width, this.padding);
|
|
366
414
|
const heading = this.headingLines(width, body.hiddenLines, this.padding);
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
415
|
+
const lines = body.lines.length
|
|
416
|
+
? [
|
|
417
|
+
...heading,
|
|
418
|
+
...body.lines.map((line) => padPreviewLine(this.style(line), width, this.padding)),
|
|
419
|
+
]
|
|
420
|
+
: heading;
|
|
421
|
+
this.collapsedMemo = {
|
|
422
|
+
width,
|
|
423
|
+
previewLines: config.previewLines,
|
|
424
|
+
hintHovered: this.hintHovered,
|
|
425
|
+
expandAction,
|
|
426
|
+
lines,
|
|
427
|
+
};
|
|
428
|
+
return lines;
|
|
372
429
|
}
|
|
373
430
|
|
|
374
|
-
invalidate() {
|
|
431
|
+
invalidate() {
|
|
432
|
+
this.collapsedMemo = undefined;
|
|
433
|
+
this.expandedBody = undefined;
|
|
434
|
+
}
|
|
375
435
|
}
|
|
376
436
|
|
|
377
437
|
function parseSummaryPart(text: string): SummaryPart | undefined {
|
|
@@ -922,6 +982,7 @@ function compactThinking(pi: ExtensionAPI) {
|
|
|
922
982
|
completedDurations.clear();
|
|
923
983
|
streamingComponents.clear();
|
|
924
984
|
expandedThinking.clear();
|
|
985
|
+
clearThinkingPreviewCache();
|
|
925
986
|
if (ctx.mode === "tui") ctx.ui.setWidget(WIDGET_ID, undefined);
|
|
926
987
|
|
|
927
988
|
if (patchInstalled) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
function modelLabel(model: any): string | undefined {
|
|
4
|
+
if (!model) return undefined;
|
|
5
|
+
const name = model.name || model.id;
|
|
6
|
+
if (!name) return undefined;
|
|
7
|
+
return model.provider ? `${model.provider} · ${name}` : name;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function apply(ctx: any): void {
|
|
11
|
+
if (!ctx?.hasUI) return;
|
|
12
|
+
ctx.ui?.setStatus?.("model", modelLabel(ctx.model));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** 用 setStatus 挂模型名,不 setFooter,不接管原生 footer。 */
|
|
16
|
+
export default function (pi: ExtensionAPI): void {
|
|
17
|
+
pi.on("session_start", async (_event, ctx) => apply(ctx));
|
|
18
|
+
pi.on("model_select", async (_event, ctx) => apply(ctx));
|
|
19
|
+
}
|
package/extensions/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { config } from "./config/config.ts";
|
|
|
5
5
|
import piAliases from "./feature/shell/aliases.ts";
|
|
6
6
|
import piStartupHeader from "./feature/shell/startup-header.ts";
|
|
7
7
|
import workingMessage from "./feature/shell/working-message.ts";
|
|
8
|
+
import modelStatus from "./feature/shell/model-status.ts";
|
|
8
9
|
|
|
9
10
|
// feature
|
|
10
11
|
import agentAutocomplete from "./feature/reference/subagent.ts";
|
|
@@ -22,6 +23,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
22
23
|
if (config.enableAliases) piAliases(pi);
|
|
23
24
|
piStartupHeader(pi);
|
|
24
25
|
if (config.enableWorkingMessage) workingMessage(pi);
|
|
26
|
+
modelStatus(pi);
|
|
25
27
|
|
|
26
28
|
// render stack:thinking controller 直接交给 style 作 query
|
|
27
29
|
markdownEnhance(pi);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* (`Ran for 8s, bash×2, read×2`),edit/write 独立标题行(`✓ write <path> (+25 -0)`),
|
|
4
4
|
* 普通工具折叠时不显示独立行;展开(Ctrl+O / fullscreen 点击)在单个工具卡中恢复
|
|
5
5
|
* compact-thinking/Pi 原生或专用 renderer。edit/write 复用 mode=on 的 rich diff
|
|
6
|
-
* 与 `
|
|
6
|
+
* 与 `editDiffCollapsedLines` / `writeDiffCollapsedLines` / `expandedPreviewMaxLines`。
|
|
7
7
|
* Agent/Task 族:调用只进摘要计数,tool 卡始终折叠(避免 pending→完成高度闪动)。
|
|
8
8
|
* 底部 Agents/Tasks 面板由 pi-subagents/pi-tasks 独立 widget 负责,不经 tool 卡外置。
|
|
9
9
|
*
|
|
@@ -322,70 +322,104 @@ function editWriteExpandedCard(theme: any): any {
|
|
|
322
322
|
);
|
|
323
323
|
}
|
|
324
324
|
|
|
325
|
-
/** compact 展开卡:外卡片保持 userMessageBg 原色;内部 tool call card
|
|
325
|
+
/** compact 展开卡:外卡片保持 userMessageBg 原色;内部 tool call card
|
|
326
326
|
* 背景更深一层且只覆盖内容区(左右内缩、上下限首尾文本行),形成嵌套层次。
|
|
327
|
-
*
|
|
328
|
-
function
|
|
327
|
+
* hits:非工具子卡(thinking)的行区间,供展开后点击 hint。 */
|
|
328
|
+
function layoutExpandedToolCard(
|
|
329
|
+
theme: any,
|
|
330
|
+
children: any[],
|
|
331
|
+
width: number,
|
|
332
|
+
): { lines: string[]; hits: Array<{ child: any; start: number; end: number }> } {
|
|
329
333
|
const slot = "userMessageBg";
|
|
330
334
|
const toolBgAnsi = darkenBgAnsi(theme, slot);
|
|
335
|
+
const innerWidth = Math.max(0, width - 2);
|
|
336
|
+
const lines: string[] = [];
|
|
337
|
+
const hits: Array<{ child: any; start: number; end: number }> = [];
|
|
338
|
+
const isThinkingPreview = (child: any) => typeof child?.setHintHovered === "function";
|
|
339
|
+
lines.push(paddedBackgroundRow(theme, slot, "", width));
|
|
340
|
+
// 展开不再显示摘要行:跳过第一个 child 的首行空白,避免白占一行
|
|
341
|
+
let skipLeadingBlank = true;
|
|
342
|
+
for (const child of children) {
|
|
343
|
+
const childLines = child.render(innerWidth);
|
|
344
|
+
// 展开的 thinking 与 tool 一样走嵌套深色卡,避免贴在外卡同色底上看不见。
|
|
345
|
+
const nest = child.__ccToolCard || (isThinkingPreview(child) && child.expanded === true);
|
|
346
|
+
if (!nest) {
|
|
347
|
+
let start = 0;
|
|
348
|
+
if (skipLeadingBlank) {
|
|
349
|
+
while (start < childLines.length && !hasVisibleText(childLines[start])) start++;
|
|
350
|
+
skipLeadingBlank = false;
|
|
351
|
+
}
|
|
352
|
+
const rangeStart = lines.length;
|
|
353
|
+
for (let i = start; i < childLines.length; i++) {
|
|
354
|
+
lines.push(paddedBackgroundRow(theme, slot, childLines[i], width));
|
|
355
|
+
}
|
|
356
|
+
if (lines.length > rangeStart) hits.push({ child, start: rangeStart, end: lines.length });
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
// 工具卡:前导 1 空行 + 首尾文本行之间的内容区(深色);不保留尾随,
|
|
360
|
+
// 相邻工具卡之间正好 1 空行,底部由卡片 padding 收尾。
|
|
361
|
+
skipLeadingBlank = false;
|
|
362
|
+
let first = -1;
|
|
363
|
+
let last = -1;
|
|
364
|
+
for (let i = 0; i < childLines.length; i++) {
|
|
365
|
+
if (hasVisibleText(childLines[i])) {
|
|
366
|
+
if (first < 0) first = i;
|
|
367
|
+
last = i;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (first < 0) {
|
|
371
|
+
for (const line of childLines) {
|
|
372
|
+
lines.push(paddedBackgroundRow(theme, slot, line, width));
|
|
373
|
+
}
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
lines.push(paddedBackgroundRow(theme, slot, "", width));
|
|
377
|
+
const rangeStart = lines.length;
|
|
378
|
+
// 子卡片内部上下各 1 行深色 padding
|
|
379
|
+
lines.push(toolCardBgRow(theme, slot, toolBgAnsi, "", width));
|
|
380
|
+
for (let i = first; i <= last; i++) {
|
|
381
|
+
lines.push(toolCardBgRow(theme, slot, toolBgAnsi, childLines[i], width));
|
|
382
|
+
}
|
|
383
|
+
lines.push(toolCardBgRow(theme, slot, toolBgAnsi, "", width));
|
|
384
|
+
if (isThinkingPreview(child)) hits.push({ child, start: rangeStart, end: lines.length });
|
|
385
|
+
}
|
|
386
|
+
lines.push(paddedBackgroundRow(theme, slot, "", width));
|
|
387
|
+
return { lines, hits };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function compactRoundCard(
|
|
391
|
+
cardItems: Array<{ child?: any; tool?: any }>,
|
|
392
|
+
toolRender: (tool: any, width: number) => string[],
|
|
393
|
+
): any {
|
|
331
394
|
const children: any[] = [];
|
|
395
|
+
for (const item of cardItems) {
|
|
396
|
+
if (item.child) children.push(item.child);
|
|
397
|
+
else if (item.tool) {
|
|
398
|
+
const tool = item.tool;
|
|
399
|
+
children.push({
|
|
400
|
+
__ccToolCard: true,
|
|
401
|
+
render: (innerWidth: number) => toolRender(tool, innerWidth),
|
|
402
|
+
invalidate: () => tool.invalidate?.(),
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
332
406
|
return {
|
|
333
407
|
children,
|
|
334
|
-
addChild(child: any) {
|
|
335
|
-
children.push(child);
|
|
336
|
-
},
|
|
337
|
-
addToolChild(child: any) {
|
|
338
|
-
child.__ccToolCard = true;
|
|
339
|
-
children.push(child);
|
|
340
|
-
},
|
|
341
408
|
render(width: number): string[] {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if (
|
|
350
|
-
let start = 0;
|
|
351
|
-
if (skipLeadingBlank) {
|
|
352
|
-
while (start < childLines.length && !hasVisibleText(childLines[start])) start++;
|
|
353
|
-
skipLeadingBlank = false;
|
|
354
|
-
}
|
|
355
|
-
for (let i = start; i < childLines.length; i++) {
|
|
356
|
-
lines.push(paddedBackgroundRow(theme, slot, childLines[i], width));
|
|
357
|
-
}
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
// 工具卡:前导 1 空行 + 首尾文本行之间的内容区(深色);不保留尾随,
|
|
361
|
-
// 相邻工具卡之间正好 1 空行,底部由卡片 padding 收尾。
|
|
362
|
-
skipLeadingBlank = false;
|
|
363
|
-
let first = -1;
|
|
364
|
-
let last = -1;
|
|
365
|
-
for (let i = 0; i < childLines.length; i++) {
|
|
366
|
-
if (hasVisibleText(childLines[i])) {
|
|
367
|
-
if (first < 0) first = i;
|
|
368
|
-
last = i;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
if (first < 0) {
|
|
372
|
-
for (const line of childLines) {
|
|
373
|
-
lines.push(paddedBackgroundRow(theme, slot, line, width));
|
|
374
|
-
}
|
|
375
|
-
continue;
|
|
376
|
-
}
|
|
377
|
-
lines.push(paddedBackgroundRow(theme, slot, "", width));
|
|
378
|
-
// 子卡片内部上下各 1 行深色 padding
|
|
379
|
-
lines.push(toolCardBgRow(theme, slot, toolBgAnsi, "", width));
|
|
380
|
-
for (let i = first; i <= last; i++) {
|
|
381
|
-
lines.push(toolCardBgRow(theme, slot, toolBgAnsi, childLines[i], width));
|
|
382
|
-
}
|
|
383
|
-
lines.push(toolCardBgRow(theme, slot, toolBgAnsi, "", width));
|
|
409
|
+
return ["", ...layoutExpandedToolCard(themeOf(), children, width).lines];
|
|
410
|
+
},
|
|
411
|
+
childAtRow(localRow: number, width: number) {
|
|
412
|
+
if (localRow < 1) return null;
|
|
413
|
+
const row = localRow - 1;
|
|
414
|
+
const { hits } = layoutExpandedToolCard(themeOf(), children, width);
|
|
415
|
+
for (const hit of hits) {
|
|
416
|
+
if (row >= hit.start && row < hit.end) return hit.child;
|
|
384
417
|
}
|
|
385
|
-
|
|
386
|
-
|
|
418
|
+
return null;
|
|
419
|
+
},
|
|
420
|
+
invalidate() {
|
|
421
|
+
for (const child of children) child.invalidate?.();
|
|
387
422
|
},
|
|
388
|
-
invalidate() {},
|
|
389
423
|
};
|
|
390
424
|
}
|
|
391
425
|
|
|
@@ -477,7 +511,7 @@ function compactEditWriteLine(
|
|
|
477
511
|
component: any,
|
|
478
512
|
width: number,
|
|
479
513
|
writeMetadata?: WriteExecutionMetadataStore,
|
|
480
|
-
options: { hint?: boolean } = {},
|
|
514
|
+
options: { hint?: boolean; flushLeft?: boolean } = {},
|
|
481
515
|
): string[] {
|
|
482
516
|
const theme = themeOf();
|
|
483
517
|
const name = String(component.toolName ?? "tool");
|
|
@@ -511,7 +545,8 @@ function compactEditWriteLine(
|
|
|
511
545
|
statsStyled = ` ${theme.fg("dim", "(")}${theme.fg("success", `+${stats.added}`)} ${theme.fg("error", `-${stats.removed}`)}${theme.fg("dim", ")")}`;
|
|
512
546
|
}
|
|
513
547
|
}
|
|
514
|
-
|
|
548
|
+
// 展开卡 Box(1,1) 已 pad;折叠行自己留 1 格前导空格
|
|
549
|
+
const iconPart = `${options.flushLeft ? "" : " "}${theme.fg(iconColor, icon)} `;
|
|
515
550
|
const namePart = theme.fg("toolTitle", name);
|
|
516
551
|
const hintText =
|
|
517
552
|
options.hint !== false && component.expanded !== true ? ` • ${showMoreHintText()}` : "";
|
|
@@ -526,6 +561,12 @@ function compactEditWriteLine(
|
|
|
526
561
|
return ["", truncateToWidth(line, width, "")];
|
|
527
562
|
}
|
|
528
563
|
|
|
564
|
+
/** expanded 写进 rich diff 闭包,折叠/展开分槽缓存,避免每帧 parseDiff。 */
|
|
565
|
+
const compactRichDiffCache = new WeakMap<
|
|
566
|
+
object,
|
|
567
|
+
{ result: unknown; collapsed?: unknown; expanded?: unknown }
|
|
568
|
+
>();
|
|
569
|
+
|
|
529
570
|
/**
|
|
530
571
|
* compact edit/write:标题行 + mode=on 同一套 rich diff。
|
|
531
572
|
* 折叠/展开都走 `renderRichToolResult`,limits 不另开一套。
|
|
@@ -540,23 +581,33 @@ function compactEditWriteLines(
|
|
|
540
581
|
const expanded = component.expanded === true;
|
|
541
582
|
const isError = result?.isError === true;
|
|
542
583
|
const isPending = !result || component.isPartial === true;
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
584
|
+
let candidate: unknown;
|
|
585
|
+
if (!isPending && writeMetadata) {
|
|
586
|
+
let entry = compactRichDiffCache.get(component);
|
|
587
|
+
if (!entry || entry.result !== result) {
|
|
588
|
+
entry = { result };
|
|
589
|
+
compactRichDiffCache.set(component, entry);
|
|
590
|
+
}
|
|
591
|
+
const slot = expanded ? "expanded" : "collapsed";
|
|
592
|
+
candidate = entry[slot];
|
|
593
|
+
if (!isRichDiffComponent(candidate)) {
|
|
594
|
+
candidate = renderRichToolResult(
|
|
595
|
+
String(component.toolName ?? ""),
|
|
596
|
+
result,
|
|
597
|
+
{
|
|
598
|
+
expanded,
|
|
599
|
+
isPartial: component.isPartial === true,
|
|
600
|
+
isError,
|
|
601
|
+
isHovered: () => isToolCallHovered(component.toolCallId),
|
|
602
|
+
},
|
|
603
|
+
theme,
|
|
604
|
+
component,
|
|
605
|
+
writeMetadata,
|
|
606
|
+
getToolDisplayConfig,
|
|
607
|
+
);
|
|
608
|
+
if (isRichDiffComponent(candidate)) entry[slot] = candidate;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
560
611
|
const hasRich = isRichDiffComponent(candidate);
|
|
561
612
|
if (hasRich) {
|
|
562
613
|
component.resultRendererComponent = candidate;
|
|
@@ -587,6 +638,7 @@ function compactEditWriteLines(
|
|
|
587
638
|
state.ccstyleIoView,
|
|
588
639
|
component.args,
|
|
589
640
|
component,
|
|
641
|
+
true, // 外层 Box(1,1) 已 pad
|
|
590
642
|
);
|
|
591
643
|
component.resultRendererComponent = detail;
|
|
592
644
|
}
|
|
@@ -598,12 +650,16 @@ function compactEditWriteLines(
|
|
|
598
650
|
const box = editWriteExpandedCard(theme);
|
|
599
651
|
box.addChild({
|
|
600
652
|
render(innerWidth: number): string[] {
|
|
601
|
-
return compactEditWriteLine(component, innerWidth, writeMetadata, {
|
|
653
|
+
return compactEditWriteLine(component, innerWidth, writeMetadata, {
|
|
654
|
+
hint: false,
|
|
655
|
+
flushLeft: true,
|
|
656
|
+
}).slice(1);
|
|
602
657
|
},
|
|
603
658
|
invalidate() {},
|
|
604
659
|
});
|
|
605
660
|
box.addChild(detail);
|
|
606
|
-
|
|
661
|
+
// 与原生 ToolExecutionComponent 一样:Spacer(1) + Box,否则贴上一条 tool 少 1 行间距
|
|
662
|
+
return ["", ...box.render(width)];
|
|
607
663
|
}
|
|
608
664
|
|
|
609
665
|
function compactAssistantLineComponent(
|
|
@@ -964,26 +1020,11 @@ export function installCompactMode(deps: CompactModeInstallDeps): CompactModeHoo
|
|
|
964
1020
|
tool.updateDisplay?.();
|
|
965
1021
|
}
|
|
966
1022
|
}
|
|
967
|
-
round.anchor.contentContainer.addChild(
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
if (item.child) box.addChild(item.child);
|
|
973
|
-
else if (item.tool) {
|
|
974
|
-
const tool = item.tool;
|
|
975
|
-
box.addToolChild({
|
|
976
|
-
render: (innerWidth: number) => patch.toolOriginalRender.call(tool, innerWidth),
|
|
977
|
-
invalidate: () => tool.invalidate?.(),
|
|
978
|
-
});
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
return ["", ...box.render(width)];
|
|
982
|
-
},
|
|
983
|
-
invalidate() {
|
|
984
|
-
for (const item of cardItems) item.child?.invalidate?.();
|
|
985
|
-
},
|
|
986
|
-
});
|
|
1023
|
+
round.anchor.contentContainer.addChild(
|
|
1024
|
+
compactRoundCard(cardItems, (tool, innerWidth) =>
|
|
1025
|
+
patch.toolOriginalRender.call(tool, innerWidth),
|
|
1026
|
+
),
|
|
1027
|
+
);
|
|
987
1028
|
// 展开卡内工具会显示 error,外层仍挂 abort/length,避免只藏在折叠工具里。
|
|
988
1029
|
appendStopStatus(round.anchor, stopStatus);
|
|
989
1030
|
return;
|
|
@@ -3,6 +3,7 @@ import { ThinkingPreviewBlock } from "../../feature/compact-thinking.ts";
|
|
|
3
3
|
import { patchRegistry, TOOL_MOUSE_OWNER_KEY } from "../../utils/patch-keys.ts";
|
|
4
4
|
import { ToolGroupComponent } from "../tool/grouping.ts";
|
|
5
5
|
import { isCompactAssistantComponent, setHoveredCompactAssistant } from "../compact-mode.ts";
|
|
6
|
+
import { isMessageDisplayComponent } from "../tool/message-display.ts";
|
|
6
7
|
import { config } from "../../config/config.ts";
|
|
7
8
|
import { isLazyProxyTui } from "../../utils/fullscreen-detect.ts";
|
|
8
9
|
import { setToolTuiFullscreen } from "../tool/show-more-hint.ts";
|
|
@@ -153,6 +154,34 @@ function updateToolSummaryHover(tui: any, packet: SgrMousePacket): void {
|
|
|
153
154
|
tui.requestRender?.();
|
|
154
155
|
}
|
|
155
156
|
|
|
157
|
+
const EXPAND_PANEL_DOUBLE_CLICK_MS = 400;
|
|
158
|
+
let lastExpandPanelClick: { card: any; at: number } | null = null;
|
|
159
|
+
|
|
160
|
+
function isExpandPanelDoubleClick(card: any): boolean {
|
|
161
|
+
const now = Date.now();
|
|
162
|
+
const prev = lastExpandPanelClick;
|
|
163
|
+
lastExpandPanelClick = { card, at: now };
|
|
164
|
+
return Boolean(prev && prev.card === card && now - prev.at <= EXPAND_PANEL_DOUBLE_CLICK_MS);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function clearExpandPanelDoubleClick(): void {
|
|
168
|
+
lastExpandPanelClick = null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function collapseExpandedCard(tui: any, card: any): boolean {
|
|
172
|
+
if (!isExpandPanelDoubleClick(card)) return false;
|
|
173
|
+
card.setExpanded(false);
|
|
174
|
+
setHoveredToolCallId(null);
|
|
175
|
+
setHoveredToolGroup(null);
|
|
176
|
+
setHoveredThinking(null);
|
|
177
|
+
setHoveredToolIo(null, null);
|
|
178
|
+
setHoveredCompactAssistant(null);
|
|
179
|
+
card.invalidate?.();
|
|
180
|
+
tui.requestRender?.();
|
|
181
|
+
clearExpandPanelDoubleClick();
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
156
185
|
function toggleToolAtMouseClick(tui: any, packet: SgrMousePacket): boolean {
|
|
157
186
|
const region = interactionRegionAt(packet);
|
|
158
187
|
if (!region) return false;
|
|
@@ -160,16 +189,9 @@ function toggleToolAtMouseClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
160
189
|
if (region.kind === "show-more") return tryOpenToolIoShowMore(region);
|
|
161
190
|
const component = region.component;
|
|
162
191
|
if (!component) return false;
|
|
163
|
-
if (region.kind === "expanded-card")
|
|
164
|
-
component.setExpanded(false);
|
|
165
|
-
setHoveredToolCallId(null);
|
|
166
|
-
setHoveredToolGroup(null);
|
|
167
|
-
setHoveredToolIo(null, null);
|
|
168
|
-
component.invalidate?.();
|
|
169
|
-
tui.requestRender?.();
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
192
|
+
if (region.kind === "expanded-card") return collapseExpandedCard(tui, component);
|
|
172
193
|
component.setExpanded(true);
|
|
194
|
+
clearExpandPanelDoubleClick();
|
|
173
195
|
setHoveredToolCallId(null);
|
|
174
196
|
setHoveredToolGroup(null);
|
|
175
197
|
setHoveredToolIo(null, null);
|
|
@@ -242,10 +264,10 @@ const FULLSCREEN_VIEWPORT_PATCH = Symbol("ccstyle.fullscreen-viewport-patch");
|
|
|
242
264
|
const FULLSCREEN_WHEEL_SCROLL_ORIGINAL = Symbol("ccstyle.fullscreen-wheel-scroll-original");
|
|
243
265
|
|
|
244
266
|
/**
|
|
245
|
-
* 官方 fullscreen 工具卡点击:collapsed hint
|
|
246
|
-
* (有且仅保持一个展开:展开前收起其他工具卡),expanded
|
|
247
|
-
* 截断头 show-more
|
|
248
|
-
* 滚动条列、含 OSC8
|
|
267
|
+
* 官方 fullscreen 工具卡点击:collapsed hint 单击展开
|
|
268
|
+
* (有且仅保持一个展开:展开前收起其他工具卡),expanded 整卡双击收起,
|
|
269
|
+
* 截断头 show-more 单击打开全量预览;回到底部按钮 scrollToBottom。
|
|
270
|
+
* 滚动条列、含 OSC8 链接行、非工具区域、展开卡单击放行官方。
|
|
249
271
|
*/
|
|
250
272
|
function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
|
|
251
273
|
const layout = tui.currentLayout;
|
|
@@ -276,7 +298,8 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
276
298
|
const isGroup = component instanceof ToolGroupComponent;
|
|
277
299
|
const isAssistant = isCompactAssistantComponent(component);
|
|
278
300
|
const isThinking = component instanceof ThinkingPreviewBlock;
|
|
279
|
-
|
|
301
|
+
const isMessage = isMessageDisplayComponent(component);
|
|
302
|
+
if (!isTool && !isGroup && !isAssistant && !isThinking && !isMessage) return false;
|
|
280
303
|
if (!component.expanded) {
|
|
281
304
|
// collapsed 仅按钮文本可展开,不能把同一行正文/留白变成点击区。
|
|
282
305
|
const hint = collapsedHintHitbox(line);
|
|
@@ -286,11 +309,14 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
286
309
|
collectFullscreenToolCards(hit.box.component, others);
|
|
287
310
|
for (const other of others) {
|
|
288
311
|
if (other !== component && other.expanded) {
|
|
312
|
+
// 展开 round 内 thinking 时不要把外层 compact 卡收起。
|
|
313
|
+
if (isThinking && isCompactAssistantComponent(other)) continue;
|
|
289
314
|
other.setExpanded(false);
|
|
290
315
|
other.invalidate?.();
|
|
291
316
|
}
|
|
292
317
|
}
|
|
293
318
|
component.setExpanded(true);
|
|
319
|
+
clearExpandPanelDoubleClick();
|
|
294
320
|
} else {
|
|
295
321
|
// 普通工具截断头 show-more:打开全量预览(不收起)。
|
|
296
322
|
const view = isTool ? component.resultRendererComponent : null;
|
|
@@ -312,8 +338,8 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
312
338
|
}
|
|
313
339
|
}
|
|
314
340
|
}
|
|
315
|
-
//
|
|
316
|
-
card
|
|
341
|
+
// 双击收起:内部工具仍归所属 group。单击放行官方(选择/链接)。
|
|
342
|
+
return collapseExpandedCard(tui, card);
|
|
317
343
|
}
|
|
318
344
|
// 点击后清 hover 高亮。
|
|
319
345
|
setHoveredToolCallId(null);
|
|
@@ -389,6 +415,9 @@ function handleFullscreenToolHover(tui: any, packet: SgrMousePacket): void {
|
|
|
389
415
|
} else if (isCompactAssistantComponent(component)) {
|
|
390
416
|
// compact 摘要行:折叠时仅提示文字高亮,展开卡整体高亮。
|
|
391
417
|
if (component.expanded || overHint) target = { kind: "assistant", component };
|
|
418
|
+
} else if (isMessageDisplayComponent(component)) {
|
|
419
|
+
// skill/compaction/branch:折叠只点 hint;展开后双击收起。
|
|
420
|
+
if (component.expanded || overHint) target = { kind: "assistant", component };
|
|
392
421
|
}
|
|
393
422
|
}
|
|
394
423
|
}
|
|
@@ -787,6 +816,7 @@ export function teardownToolMouseInteraction(
|
|
|
787
816
|
setHoveredThinking(null);
|
|
788
817
|
setHoveredToolIo(null, null);
|
|
789
818
|
setHoveredCompactAssistant(null);
|
|
819
|
+
clearExpandPanelDoubleClick();
|
|
790
820
|
try {
|
|
791
821
|
if (isLazyProxyTui(getToolMouseTui())) releaseFullscreenToolMouseMotion(getToolMouseTui());
|
|
792
822
|
else getToolMouseTui()?.terminal?.write?.(TOOL_MOUSE_DISABLE);
|
|
@@ -2,6 +2,7 @@ import { visibleWidth } from "@earendil-works/pi-tui";
|
|
|
2
2
|
import { ThinkingPreviewBlock } from "../../feature/compact-thinking.ts";
|
|
3
3
|
import { isCompactAssistantComponent } from "../compact-mode.ts";
|
|
4
4
|
import { ToolGroupComponent } from "../tool/grouping.ts";
|
|
5
|
+
import { isMessageDisplayComponent } from "../tool/message-display.ts";
|
|
5
6
|
import { isToolExecutionComponent, stripTerminalSequencesPreservingLayout } from "./packets.ts";
|
|
6
7
|
import { getScrollButtonWidget } from "./scroll.ts";
|
|
7
8
|
|
|
@@ -77,6 +78,29 @@ export function isScrollbarColumnAt(layout: any, x: number): boolean {
|
|
|
77
78
|
return hit;
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
/** compact 展开卡用 childAtRow 映射 thinking;其余按 render 行数下钻。 */
|
|
82
|
+
function nestedChildAtRow(node: any, localRow: number, width: number): any {
|
|
83
|
+
if (typeof node?.childAtRow === "function") {
|
|
84
|
+
return node.childAtRow(localRow, width);
|
|
85
|
+
}
|
|
86
|
+
if (!Array.isArray(node?.children)) return null;
|
|
87
|
+
let offset = 0;
|
|
88
|
+
for (const child of node.children) {
|
|
89
|
+
let count = 0;
|
|
90
|
+
try {
|
|
91
|
+
const rendered = child.render?.(width);
|
|
92
|
+
if (Array.isArray(rendered)) count = rendered.length;
|
|
93
|
+
} catch {
|
|
94
|
+
count = 0;
|
|
95
|
+
}
|
|
96
|
+
if (localRow < offset + count) {
|
|
97
|
+
return nestedChildAtRow(child, localRow - offset, width);
|
|
98
|
+
}
|
|
99
|
+
offset += count;
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
80
104
|
/**
|
|
81
105
|
* 布局 leaf box 的组件通常是容器(documentContainer/dock 容器等),工具卡与
|
|
82
106
|
* widget 在其 children 内。按局部行遍历组件树,定位实际命中的子组件。
|
|
@@ -95,12 +119,21 @@ export function componentAtLocalRow(
|
|
|
95
119
|
return { component, row: localRow };
|
|
96
120
|
}
|
|
97
121
|
if (isCompactAssistantComponent(component)) {
|
|
98
|
-
//
|
|
122
|
+
// 折叠:整行摘要。展开:先命中内部 thinking hint,其余仍归外层卡片。
|
|
123
|
+
if (component.expanded === true) {
|
|
124
|
+
const inner = nestedChildAtRow(component, localRow, width);
|
|
125
|
+
if (inner instanceof ThinkingPreviewBlock) {
|
|
126
|
+
return { component: inner, row: localRow };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
99
129
|
return { component, row: localRow };
|
|
100
130
|
}
|
|
101
131
|
if (component instanceof ThinkingPreviewBlock) {
|
|
102
132
|
return { component, row: localRow };
|
|
103
133
|
}
|
|
134
|
+
if (isMessageDisplayComponent(component)) {
|
|
135
|
+
return { component, row: localRow };
|
|
136
|
+
}
|
|
104
137
|
if (component === getScrollButtonWidget()) {
|
|
105
138
|
return { component, row: localRow };
|
|
106
139
|
}
|
|
@@ -139,7 +172,8 @@ export function collectFullscreenToolCards(
|
|
|
139
172
|
isToolExecutionComponent(component) ||
|
|
140
173
|
component instanceof ToolGroupComponent ||
|
|
141
174
|
isCompactAssistantComponent(component) ||
|
|
142
|
-
component instanceof ThinkingPreviewBlock
|
|
175
|
+
component instanceof ThinkingPreviewBlock ||
|
|
176
|
+
isMessageDisplayComponent(component)
|
|
143
177
|
) {
|
|
144
178
|
out.push(component);
|
|
145
179
|
return;
|
|
@@ -39,7 +39,7 @@ export function displayConfigCacheKey(config: ToolDisplayConfig): string {
|
|
|
39
39
|
config.diffViewMode,
|
|
40
40
|
config.diffIndicatorMode,
|
|
41
41
|
String(config.diffSplitMinWidth),
|
|
42
|
-
String(config.
|
|
42
|
+
String(config.editDiffCollapsedLines),
|
|
43
43
|
String(config.writeDiffCollapsedLines),
|
|
44
44
|
config.diffWordWrap ? "1" : "0",
|
|
45
45
|
String(config.expandedPreviewMaxLines),
|
|
@@ -143,7 +143,7 @@ export function renderEditDiffResult(
|
|
|
143
143
|
const headerRows = renderHeaderRows(parsed.stats, mode, safeWidth, theme);
|
|
144
144
|
const displayLimit = resolveDiffDisplayLimit(
|
|
145
145
|
options.expanded,
|
|
146
|
-
live.
|
|
146
|
+
live.editDiffCollapsedLines,
|
|
147
147
|
live.expandedPreviewMaxLines,
|
|
148
148
|
);
|
|
149
149
|
const processBudget = resolveDiffProcessBudget(displayLimit, wordWrap);
|
|
@@ -177,7 +177,7 @@ export function renderEditDiffResult(
|
|
|
177
177
|
bodyRows,
|
|
178
178
|
safeWidth,
|
|
179
179
|
options.expanded,
|
|
180
|
-
live.
|
|
180
|
+
live.editDiffCollapsedLines,
|
|
181
181
|
live.expandedPreviewMaxLines,
|
|
182
182
|
parsed.stats.hunks,
|
|
183
183
|
theme,
|
|
@@ -41,20 +41,20 @@ export function resolveDiffDisplayLimit(
|
|
|
41
41
|
: DEFAULT_TOOL_DISPLAY_CONFIG.expandedPreviewMaxLines;
|
|
42
42
|
const collapsedLimit = Number.isFinite(maxCollapsedLines)
|
|
43
43
|
? maxCollapsedLines
|
|
44
|
-
: DEFAULT_TOOL_DISPLAY_CONFIG.
|
|
44
|
+
: DEFAULT_TOOL_DISPLAY_CONFIG.editDiffCollapsedLines;
|
|
45
45
|
return expanded ? Math.max(0, expandedLimit) : Math.max(1, collapsedLimit);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
/** Write collapsed body limit. Missing/NaN falls back to edit's `
|
|
48
|
+
/** Write collapsed body limit. Missing/NaN falls back to edit's `editDiffCollapsedLines`. */
|
|
49
49
|
export function resolveWriteCollapsedLimit(
|
|
50
|
-
config: Pick<ToolDisplayConfig, "writeDiffCollapsedLines" | "
|
|
50
|
+
config: Pick<ToolDisplayConfig, "writeDiffCollapsedLines" | "editDiffCollapsedLines">,
|
|
51
51
|
): number {
|
|
52
52
|
const value = config.writeDiffCollapsedLines;
|
|
53
53
|
if (Number.isFinite(value)) {
|
|
54
54
|
return Math.max(0, value);
|
|
55
55
|
}
|
|
56
|
-
const fallback = Number.isFinite(config.
|
|
57
|
-
? config.
|
|
56
|
+
const fallback = Number.isFinite(config.editDiffCollapsedLines)
|
|
57
|
+
? config.editDiffCollapsedLines
|
|
58
58
|
: DEFAULT_TOOL_DISPLAY_CONFIG.writeDiffCollapsedLines;
|
|
59
59
|
return Math.max(0, fallback);
|
|
60
60
|
}
|
|
@@ -180,11 +180,13 @@ export class ToolGroupComponent extends Container {
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
addTool(tool: any): void {
|
|
183
|
+
this.settledCache = undefined;
|
|
183
184
|
this.children.push(tool);
|
|
184
185
|
tool[PARENT_KEY] = this;
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
releaseTools(): any[] {
|
|
189
|
+
this.settledCache = undefined;
|
|
188
190
|
const tools = [...this.children];
|
|
189
191
|
this.children.length = 0;
|
|
190
192
|
this.patch.groups.delete(this);
|
|
@@ -192,18 +194,21 @@ export class ToolGroupComponent extends Container {
|
|
|
192
194
|
}
|
|
193
195
|
|
|
194
196
|
removeTool(tool: any): void {
|
|
197
|
+
this.settledCache = undefined;
|
|
195
198
|
const index = this.children.indexOf(tool);
|
|
196
199
|
if (index >= 0) this.children.splice(index, 1);
|
|
197
200
|
if (tool?.[PARENT_KEY] === this) delete tool[PARENT_KEY];
|
|
198
201
|
}
|
|
199
202
|
|
|
200
203
|
setExpanded(expanded: boolean): void {
|
|
204
|
+
if (this._expanded !== expanded) this.settledCache = undefined;
|
|
201
205
|
this._expanded = expanded;
|
|
202
206
|
for (const tool of this.children)
|
|
203
207
|
(tool as Component & { setExpanded?: (expanded: boolean) => void }).setExpanded?.(expanded);
|
|
204
208
|
}
|
|
205
209
|
|
|
206
210
|
setHintHovered(hovered: boolean): void {
|
|
211
|
+
if (this.hintHovered !== hovered) this.settledCache = undefined;
|
|
207
212
|
this.hintHovered = hovered;
|
|
208
213
|
}
|
|
209
214
|
|
|
@@ -232,6 +237,7 @@ export class ToolGroupComponent extends Container {
|
|
|
232
237
|
}
|
|
233
238
|
|
|
234
239
|
invalidate(): void {
|
|
240
|
+
this.settledCache = undefined;
|
|
235
241
|
for (const tool of this.children) tool.invalidate?.();
|
|
236
242
|
}
|
|
237
243
|
|
|
@@ -134,7 +134,7 @@ export function installMessageDisplayRendering(): () => void {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
/** 三个消息组件共享的结构特征;CustomMessageComponent 用 rebuild 无 updateDisplay,不命中。 */
|
|
137
|
-
function isMessageDisplayComponent(value: any): boolean {
|
|
137
|
+
export function isMessageDisplayComponent(value: any): boolean {
|
|
138
138
|
return Boolean(
|
|
139
139
|
value &&
|
|
140
140
|
typeof value === "object" &&
|