pi-cc-extensions 0.8.48 → 0.8.49
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 +17 -36
- package/README.md +13 -12
- package/assets/readme/welcome.webp +0 -0
- package/extensions/config/config.ts +245 -0
- package/extensions/config/panel.ts +469 -0
- package/extensions/{compact-thinking.ts → feature/compact-thinking.ts} +43 -6
- package/extensions/{markdown-enhance.ts → feature/markdown-enhance.ts} +88 -62
- package/extensions/{pi-startup-header.ts → feature/pi-startup-header.ts} +129 -56
- package/extensions/index.ts +9 -9
- package/extensions/renderer/index.ts +890 -0
- package/extensions/renderer/mouse-interaction.ts +1344 -0
- package/extensions/renderer/show-more-hint.ts +40 -0
- package/extensions/{tool-diff → renderer/tool-diff}/diff-renderer.ts +3 -2
- package/extensions/{tool-diff → renderer/tool-diff}/line-width-safety.ts +2 -1
- package/extensions/{tool-grouping.ts → renderer/tool-grouping.ts} +71 -10
- package/extensions/renderer/tool-result.ts +717 -0
- package/package.json +1 -1
- package/themes/cc-dark.json +2 -2
- package/themes/cc-light.json +2 -2
- package/extensions/claude-code-style.ts +0 -3348
- /package/extensions/{agent-autocomplete.ts → feature/agent-autocomplete.ts} +0 -0
- /package/extensions/{context.ts → feature/context.ts} +0 -0
- /package/extensions/{pi-aliases.ts → feature/pi-aliases.ts} +0 -0
- /package/extensions/{session-reference-core.ts → feature/session-reference-core.ts} +0 -0
- /package/extensions/{session-reference.ts → feature/session-reference.ts} +0 -0
- /package/extensions/{working-message.ts → feature/working-message.ts} +0 -0
- /package/extensions/{tool-diff → renderer/tool-diff}/ATTRIBUTION.md +0 -0
- /package/extensions/{tool-diff → renderer/tool-diff}/ansi-utils.ts +0 -0
- /package/extensions/{tool-diff → renderer/tool-diff}/diff-presentation.ts +0 -0
- /package/extensions/{tool-diff → renderer/tool-diff}/index.ts +0 -0
- /package/extensions/{tool-diff → renderer/tool-diff}/render-utils.ts +0 -0
- /package/extensions/{tool-diff → renderer/tool-diff}/shiki-highlight.ts +0 -0
- /package/extensions/{tool-diff → renderer/tool-diff}/types.ts +0 -0
- /package/extensions/{tool-diff → renderer/tool-diff}/write-display-utils.ts +0 -0
- /package/extensions/{tool-diff → renderer/tool-diff}/write-execution.ts +0 -0
- /package/extensions/{fullscreen-detect.ts → utils/fullscreen-detect.ts} +0 -0
- /package/extensions/{tool-loading-icon.ts → utils/tool-loading-icon.ts} +0 -0
- /package/extensions/{tool-result-sanitize.ts → utils/tool-result-sanitize.ts} +0 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /ccstyle 配置面板 UI。
|
|
3
|
+
*
|
|
4
|
+
* 渲染副作用(applyStyleMode / refreshCurrentTranscript)由 renderer 经
|
|
5
|
+
* CcstylePanelHooks 注入,避免 config → renderer 循环依赖。
|
|
6
|
+
*/
|
|
7
|
+
import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { SettingsList, matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
|
|
9
|
+
import type { CompactThinkingController } from "../feature/compact-thinking.ts";
|
|
10
|
+
import { applyStartupHeader } from "../feature/pi-startup-header.ts";
|
|
11
|
+
import type { ToolGroupingHooks } from "../renderer/tool-grouping.ts";
|
|
12
|
+
import type { DiffIndicatorMode, DiffViewMode } from "../renderer/tool-diff/index.ts";
|
|
13
|
+
import {
|
|
14
|
+
config,
|
|
15
|
+
DEFAULT_CONFIG,
|
|
16
|
+
DIFF_COLLAPSED_LINES_VALUES,
|
|
17
|
+
DIFF_INDICATOR_MODES,
|
|
18
|
+
DIFF_SPLIT_MIN_WIDTH_VALUES,
|
|
19
|
+
DIFF_VIEW_MODES,
|
|
20
|
+
EXCLUDE_RENDERER_CANDIDATES,
|
|
21
|
+
EXPANDED_PREVIEW_MAX_LINES_VALUES,
|
|
22
|
+
formatExcludeRenderers,
|
|
23
|
+
getCompactThinkingConfig,
|
|
24
|
+
nearestPreset,
|
|
25
|
+
pickPositiveInt,
|
|
26
|
+
pickPositiveNumber,
|
|
27
|
+
saveConfig,
|
|
28
|
+
SCROLL_STEP_LINES_VALUES,
|
|
29
|
+
THINKING_ANIMATION_INTERVAL_VALUES,
|
|
30
|
+
THINKING_PREVIEW_LINES_VALUES,
|
|
31
|
+
type CompactStyleMode,
|
|
32
|
+
} from "./config.ts";
|
|
33
|
+
|
|
34
|
+
/** renderer 注入的渲染副作用,面板自身不触碰渲染状态。 */
|
|
35
|
+
export type CcstylePanelHooks = {
|
|
36
|
+
applyStyleMode: (mode: CompactStyleMode, ctx: any, toolGrouping?: ToolGroupingHooks) => void;
|
|
37
|
+
refreshCurrentTranscript: (ctx?: any, toolGrouping?: ToolGroupingHooks) => void;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function modeSettingDescription(mode: CompactStyleMode): string {
|
|
41
|
+
if (mode === "off") {
|
|
42
|
+
return "Pi native tool rendering. Diff options below still apply independently.";
|
|
43
|
+
}
|
|
44
|
+
return "Claude Code style with rich edit/write diffs. Tune diff options below.";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function excludeRenderersDescription(names: readonly string[]): string {
|
|
48
|
+
return names.length === 0
|
|
49
|
+
? "No tools excluded. Agent always keeps its dedicated renderer. Enter to toggle common tools."
|
|
50
|
+
: `Native renderer for: ${names.join(", ")}. Agent is always native. Enter to toggle.`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function diffViewModeDescription(mode: DiffViewMode): string {
|
|
54
|
+
if (mode === "split") return "Force side-by-side diff when width allows; otherwise unified.";
|
|
55
|
+
if (mode === "unified") return "Always render a single unified diff column.";
|
|
56
|
+
return "Auto: split when terminal is wide enough, otherwise unified.";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function diffIndicatorDescription(mode: DiffIndicatorMode): string {
|
|
60
|
+
if (mode === "classic") return "Classic +/- gutters on changed lines.";
|
|
61
|
+
if (mode === "none") return "No change indicators; rely on color alone.";
|
|
62
|
+
return "Vertical bar indicators on changed lines (default).";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function buildExcludeRenderersSubmenu(
|
|
66
|
+
onClose: () => void,
|
|
67
|
+
onLiveChange: () => void,
|
|
68
|
+
): {
|
|
69
|
+
render: (width: number) => string[];
|
|
70
|
+
invalidate: () => void;
|
|
71
|
+
handleInput: (data: string) => void;
|
|
72
|
+
} {
|
|
73
|
+
const candidates = [
|
|
74
|
+
...new Set([...EXCLUDE_RENDERER_CANDIDATES, ...config.excludeRenderers]),
|
|
75
|
+
].sort((a, b) => a.localeCompare(b));
|
|
76
|
+
const items = candidates.map((name) => ({
|
|
77
|
+
id: name,
|
|
78
|
+
label: name,
|
|
79
|
+
description:
|
|
80
|
+
name === "Agent"
|
|
81
|
+
? "Agent always uses its dedicated renderer and cannot be forced through ccstyle."
|
|
82
|
+
: `Use Pi native renderer for ${name} instead of Claude Code styling.`,
|
|
83
|
+
currentValue: config.excludeRenderers.includes(name) ? "exclude" : "style",
|
|
84
|
+
values: ["style", "exclude"],
|
|
85
|
+
}));
|
|
86
|
+
const list = new SettingsList(
|
|
87
|
+
items,
|
|
88
|
+
Math.min(8, Math.max(4, items.length)),
|
|
89
|
+
getSettingsListTheme(),
|
|
90
|
+
(id: string, value: string) => {
|
|
91
|
+
const excluded = new Set(config.excludeRenderers);
|
|
92
|
+
if (value === "exclude") excluded.add(id);
|
|
93
|
+
else excluded.delete(id);
|
|
94
|
+
config.excludeRenderers = [...excluded].sort((a, b) => a.localeCompare(b));
|
|
95
|
+
saveConfig();
|
|
96
|
+
onLiveChange();
|
|
97
|
+
},
|
|
98
|
+
() => onClose(),
|
|
99
|
+
{ enableSearch: candidates.length > 8 },
|
|
100
|
+
);
|
|
101
|
+
return {
|
|
102
|
+
render: (width: number) => [
|
|
103
|
+
...list.render(width),
|
|
104
|
+
"",
|
|
105
|
+
// Extra hint: Esc returns to the Style section list.
|
|
106
|
+
truncateToWidth(" Esc back to Style settings", width),
|
|
107
|
+
],
|
|
108
|
+
invalidate: () => list.invalidate(),
|
|
109
|
+
handleInput: (data: string) => list.handleInput(data),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Section tabs for /ccstyle — matches Zentui-style "A / B / C" headers. */
|
|
114
|
+
type CcstyleSection = {
|
|
115
|
+
id: "style" | "editor" | "diff" | "thinking" | "feature";
|
|
116
|
+
label: string;
|
|
117
|
+
items: any[];
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
function isForwardTabKey(data: string): boolean {
|
|
121
|
+
return data === "\t" || matchesKey(data, "tab");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isBackTabKey(data: string): boolean {
|
|
125
|
+
// CSI Z is the common terminal encoding for Shift+Tab.
|
|
126
|
+
return data === "\x1b[Z" || matchesKey(data, "shift+tab");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function renderPanelRule(theme: any, width: number): string {
|
|
130
|
+
return theme.fg("dim", "─".repeat(Math.max(0, width)));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function renderSectionTabBar(
|
|
134
|
+
theme: any,
|
|
135
|
+
sections: readonly { label: string }[],
|
|
136
|
+
activeIndex: number,
|
|
137
|
+
width: number,
|
|
138
|
+
): string {
|
|
139
|
+
const pieces: string[] = [];
|
|
140
|
+
for (let i = 0; i < sections.length; i++) {
|
|
141
|
+
if (i > 0) pieces.push(theme.fg("dim", " / "));
|
|
142
|
+
const label = sections[i]?.label ?? "";
|
|
143
|
+
pieces.push(
|
|
144
|
+
i === activeIndex
|
|
145
|
+
? theme.fg("text", typeof theme.bold === "function" ? theme.bold(label) : label)
|
|
146
|
+
: theme.fg("dim", label),
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return truncateToWidth(pieces.join(""), Math.max(0, width));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function showCcstylePanel(
|
|
153
|
+
ctx: any,
|
|
154
|
+
hooks: CcstylePanelHooks,
|
|
155
|
+
toolGrouping?: ToolGroupingHooks,
|
|
156
|
+
compactThinking?: CompactThinkingController,
|
|
157
|
+
): Promise<void> {
|
|
158
|
+
if (ctx?.mode !== "tui" || !ctx?.hasUI || typeof ctx.ui?.custom !== "function") {
|
|
159
|
+
ctx.ui?.notify?.("/ccstyle requires TUI mode", "warning");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
await ctx.ui.custom((tui: any, theme: any, _keybindings: any, done: () => void) => {
|
|
164
|
+
const modeSetting = {
|
|
165
|
+
id: "mode",
|
|
166
|
+
label: "Mode",
|
|
167
|
+
description: modeSettingDescription(config.mode),
|
|
168
|
+
currentValue: config.mode,
|
|
169
|
+
values: ["on", "off"],
|
|
170
|
+
};
|
|
171
|
+
// Tracks whether the Exclude-tools submenu is open so Tab switches sections
|
|
172
|
+
// only at the top level (mirrors Zentui settings: Tab = switch sections).
|
|
173
|
+
let excludeSubmenuOpen = false;
|
|
174
|
+
const excludeSetting = {
|
|
175
|
+
id: "excludeRenderers",
|
|
176
|
+
label: "Exclude tools",
|
|
177
|
+
description: excludeRenderersDescription(config.excludeRenderers),
|
|
178
|
+
currentValue: formatExcludeRenderers(config.excludeRenderers),
|
|
179
|
+
submenu: (_current: string, closeSubmenu: (selected?: string) => void) => {
|
|
180
|
+
excludeSubmenuOpen = true;
|
|
181
|
+
return buildExcludeRenderersSubmenu(
|
|
182
|
+
() => {
|
|
183
|
+
excludeSubmenuOpen = false;
|
|
184
|
+
excludeSetting.currentValue = formatExcludeRenderers(config.excludeRenderers);
|
|
185
|
+
excludeSetting.description = excludeRenderersDescription(config.excludeRenderers);
|
|
186
|
+
closeSubmenu();
|
|
187
|
+
},
|
|
188
|
+
() => {
|
|
189
|
+
excludeSetting.currentValue = formatExcludeRenderers(config.excludeRenderers);
|
|
190
|
+
excludeSetting.description = excludeRenderersDescription(config.excludeRenderers);
|
|
191
|
+
hooks.refreshCurrentTranscript(ctx);
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
const diffViewSetting = {
|
|
197
|
+
id: "diffViewMode",
|
|
198
|
+
label: "Diff layout",
|
|
199
|
+
description: diffViewModeDescription(config.diffViewMode),
|
|
200
|
+
currentValue: config.diffViewMode,
|
|
201
|
+
values: [...DIFF_VIEW_MODES],
|
|
202
|
+
};
|
|
203
|
+
const diffIndicatorSetting = {
|
|
204
|
+
id: "diffIndicatorMode",
|
|
205
|
+
label: "Diff indicator",
|
|
206
|
+
description: diffIndicatorDescription(config.diffIndicatorMode),
|
|
207
|
+
currentValue: config.diffIndicatorMode,
|
|
208
|
+
values: [...DIFF_INDICATOR_MODES],
|
|
209
|
+
};
|
|
210
|
+
const diffSplitSetting = {
|
|
211
|
+
id: "diffSplitMinWidth",
|
|
212
|
+
label: "Split min width",
|
|
213
|
+
description: "Minimum terminal width before auto/split layout uses side-by-side columns.",
|
|
214
|
+
currentValue: nearestPreset(config.diffSplitMinWidth, DIFF_SPLIT_MIN_WIDTH_VALUES),
|
|
215
|
+
values: [...DIFF_SPLIT_MIN_WIDTH_VALUES],
|
|
216
|
+
};
|
|
217
|
+
const diffCollapsedSetting = {
|
|
218
|
+
id: "diffCollapsedLines",
|
|
219
|
+
label: "Collapsed lines",
|
|
220
|
+
description: "How many diff body lines to show before the expand hint (Ctrl+O / click).",
|
|
221
|
+
currentValue: nearestPreset(config.diffCollapsedLines, DIFF_COLLAPSED_LINES_VALUES),
|
|
222
|
+
values: [...DIFF_COLLAPSED_LINES_VALUES],
|
|
223
|
+
};
|
|
224
|
+
const diffWordWrapSetting = {
|
|
225
|
+
id: "diffWordWrap",
|
|
226
|
+
label: "Diff word wrap",
|
|
227
|
+
description: config.diffWordWrap
|
|
228
|
+
? "Long diff lines wrap within the panel width."
|
|
229
|
+
: "Long diff lines are truncated to the panel width.",
|
|
230
|
+
currentValue: config.diffWordWrap ? "on" : "off",
|
|
231
|
+
values: ["on", "off"],
|
|
232
|
+
};
|
|
233
|
+
const expandedMaxSetting = {
|
|
234
|
+
id: "expandedPreviewMaxLines",
|
|
235
|
+
label: "Expanded max lines",
|
|
236
|
+
description:
|
|
237
|
+
"Max Output/diff body lines when expanded. Default 40 keeps the TUI compact; raise for large dumps.",
|
|
238
|
+
currentValue: nearestPreset(
|
|
239
|
+
config.expandedPreviewMaxLines,
|
|
240
|
+
EXPANDED_PREVIEW_MAX_LINES_VALUES,
|
|
241
|
+
),
|
|
242
|
+
values: [...EXPANDED_PREVIEW_MAX_LINES_VALUES],
|
|
243
|
+
};
|
|
244
|
+
const thinkingTitleSetting = {
|
|
245
|
+
id: "useSummaryTitlesAsThinkingTitle",
|
|
246
|
+
label: "Summary title",
|
|
247
|
+
description: "Use the latest provider summary as the active thinking title.",
|
|
248
|
+
currentValue: config.useSummaryTitlesAsThinkingTitle ? "on" : "off",
|
|
249
|
+
values: ["on", "off"],
|
|
250
|
+
};
|
|
251
|
+
const thinkingPreviewSetting = {
|
|
252
|
+
id: "previewLines",
|
|
253
|
+
label: "Preview lines",
|
|
254
|
+
description: "Thinking preview lines; 0 hides the preview body.",
|
|
255
|
+
currentValue: nearestPreset(config.previewLines, THINKING_PREVIEW_LINES_VALUES),
|
|
256
|
+
values: [...THINKING_PREVIEW_LINES_VALUES],
|
|
257
|
+
};
|
|
258
|
+
const thinkingAnimationSetting = {
|
|
259
|
+
id: "animationIntervalMs",
|
|
260
|
+
label: "Animation interval ms",
|
|
261
|
+
description: "Thinking title animation interval for the next thinking run.",
|
|
262
|
+
currentValue: nearestPreset(config.animationIntervalMs, THINKING_ANIMATION_INTERVAL_VALUES),
|
|
263
|
+
values: [...THINKING_ANIMATION_INTERVAL_VALUES],
|
|
264
|
+
};
|
|
265
|
+
const startupHeaderSetting = {
|
|
266
|
+
id: "showStartupHeader",
|
|
267
|
+
label: "Startup header",
|
|
268
|
+
description: config.showStartupHeader
|
|
269
|
+
? "Show the custom startup header (logo + tips) on new sessions."
|
|
270
|
+
: "Use Pi's native startup header instead.",
|
|
271
|
+
currentValue: config.showStartupHeader ? "on" : "off",
|
|
272
|
+
values: ["on", "off"],
|
|
273
|
+
};
|
|
274
|
+
const scrollStepSetting = {
|
|
275
|
+
id: "scrollStepLines",
|
|
276
|
+
label: "Scroll step",
|
|
277
|
+
description: "Mouse wheel scroll lines in fullscreen mode.",
|
|
278
|
+
currentValue: nearestPreset(config.scrollStepLines, SCROLL_STEP_LINES_VALUES),
|
|
279
|
+
values: [...SCROLL_STEP_LINES_VALUES],
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const onSettingChange = (id: string, value: string) => {
|
|
283
|
+
switch (id) {
|
|
284
|
+
case "mode":
|
|
285
|
+
modeSetting.description = modeSettingDescription(value as CompactStyleMode);
|
|
286
|
+
hooks.applyStyleMode(value as CompactStyleMode, ctx, toolGrouping);
|
|
287
|
+
return;
|
|
288
|
+
case "excludeRenderers":
|
|
289
|
+
excludeSetting.currentValue = formatExcludeRenderers(config.excludeRenderers);
|
|
290
|
+
excludeSetting.description = excludeRenderersDescription(config.excludeRenderers);
|
|
291
|
+
return;
|
|
292
|
+
case "diffViewMode":
|
|
293
|
+
config.diffViewMode = value as DiffViewMode;
|
|
294
|
+
diffViewSetting.description = diffViewModeDescription(config.diffViewMode);
|
|
295
|
+
break;
|
|
296
|
+
case "diffIndicatorMode":
|
|
297
|
+
config.diffIndicatorMode = value as DiffIndicatorMode;
|
|
298
|
+
diffIndicatorSetting.description = diffIndicatorDescription(config.diffIndicatorMode);
|
|
299
|
+
break;
|
|
300
|
+
case "diffSplitMinWidth":
|
|
301
|
+
config.diffSplitMinWidth = pickPositiveInt(
|
|
302
|
+
value,
|
|
303
|
+
DEFAULT_CONFIG.diffSplitMinWidth,
|
|
304
|
+
40,
|
|
305
|
+
300,
|
|
306
|
+
);
|
|
307
|
+
break;
|
|
308
|
+
case "diffCollapsedLines":
|
|
309
|
+
config.diffCollapsedLines = pickPositiveInt(
|
|
310
|
+
value,
|
|
311
|
+
DEFAULT_CONFIG.diffCollapsedLines,
|
|
312
|
+
1,
|
|
313
|
+
500,
|
|
314
|
+
);
|
|
315
|
+
break;
|
|
316
|
+
case "diffWordWrap":
|
|
317
|
+
config.diffWordWrap = value === "on";
|
|
318
|
+
diffWordWrapSetting.description = config.diffWordWrap
|
|
319
|
+
? "Long diff lines wrap within the panel width."
|
|
320
|
+
: "Long diff lines are truncated to the panel width.";
|
|
321
|
+
break;
|
|
322
|
+
case "expandedPreviewMaxLines":
|
|
323
|
+
config.expandedPreviewMaxLines = pickPositiveInt(
|
|
324
|
+
value,
|
|
325
|
+
DEFAULT_CONFIG.expandedPreviewMaxLines,
|
|
326
|
+
10,
|
|
327
|
+
50_000,
|
|
328
|
+
);
|
|
329
|
+
break;
|
|
330
|
+
case "useSummaryTitlesAsThinkingTitle":
|
|
331
|
+
config.useSummaryTitlesAsThinkingTitle = value === "on";
|
|
332
|
+
break;
|
|
333
|
+
case "previewLines":
|
|
334
|
+
config.previewLines = pickPositiveInt(value, DEFAULT_CONFIG.previewLines, 0);
|
|
335
|
+
break;
|
|
336
|
+
case "animationIntervalMs":
|
|
337
|
+
config.animationIntervalMs = pickPositiveNumber(
|
|
338
|
+
value,
|
|
339
|
+
DEFAULT_CONFIG.animationIntervalMs,
|
|
340
|
+
);
|
|
341
|
+
break;
|
|
342
|
+
case "showStartupHeader":
|
|
343
|
+
config.showStartupHeader = value === "on";
|
|
344
|
+
startupHeaderSetting.description = config.showStartupHeader
|
|
345
|
+
? "Show the custom startup header (logo + tips) on new sessions."
|
|
346
|
+
: "Use Pi's native startup header instead.";
|
|
347
|
+
// 实时切换:on → 自定义 header;off → 官方默认 header。
|
|
348
|
+
applyStartupHeader(ctx);
|
|
349
|
+
break;
|
|
350
|
+
case "scrollStepLines":
|
|
351
|
+
config.scrollStepLines = pickPositiveInt(
|
|
352
|
+
value,
|
|
353
|
+
DEFAULT_CONFIG.scrollStepLines,
|
|
354
|
+
1,
|
|
355
|
+
50,
|
|
356
|
+
);
|
|
357
|
+
break;
|
|
358
|
+
default:
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
saveConfig();
|
|
362
|
+
compactThinking?.updateConfig(getCompactThinkingConfig());
|
|
363
|
+
hooks.refreshCurrentTranscript(ctx);
|
|
364
|
+
ctx.ui.notify(`Updated ${id}: ${value}`, "info");
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const sections: CcstyleSection[] = [
|
|
368
|
+
{
|
|
369
|
+
id: "style",
|
|
370
|
+
label: "Style",
|
|
371
|
+
items: [modeSetting, excludeSetting],
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
id: "diff",
|
|
375
|
+
label: "Diff",
|
|
376
|
+
items: [
|
|
377
|
+
diffViewSetting,
|
|
378
|
+
diffIndicatorSetting,
|
|
379
|
+
diffSplitSetting,
|
|
380
|
+
diffCollapsedSetting,
|
|
381
|
+
diffWordWrapSetting,
|
|
382
|
+
expandedMaxSetting,
|
|
383
|
+
],
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
id: "thinking",
|
|
387
|
+
label: "Thinking",
|
|
388
|
+
items: [thinkingTitleSetting, thinkingPreviewSetting, thinkingAnimationSetting],
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
id: "feature",
|
|
392
|
+
label: "Feature",
|
|
393
|
+
items: [startupHeaderSetting, scrollStepSetting],
|
|
394
|
+
},
|
|
395
|
+
];
|
|
396
|
+
|
|
397
|
+
let activeSection = 0;
|
|
398
|
+
const settingsTheme = getSettingsListTheme();
|
|
399
|
+
const lists = sections.map(
|
|
400
|
+
(section) =>
|
|
401
|
+
new SettingsList(
|
|
402
|
+
section.items,
|
|
403
|
+
Math.min(8, Math.max(section.items.length, 1)),
|
|
404
|
+
settingsTheme,
|
|
405
|
+
onSettingChange,
|
|
406
|
+
() => done(),
|
|
407
|
+
{ enableSearch: false },
|
|
408
|
+
),
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
const activeList = () => lists[activeSection]!;
|
|
412
|
+
|
|
413
|
+
const switchSection = (delta: number) => {
|
|
414
|
+
if (excludeSubmenuOpen) return;
|
|
415
|
+
activeSection = (activeSection + delta + sections.length) % sections.length;
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
render(width: number): string[] {
|
|
420
|
+
const safeWidth = Math.max(0, Math.floor(width));
|
|
421
|
+
const rule = renderPanelRule(theme, safeWidth);
|
|
422
|
+
const body = activeList().render(safeWidth);
|
|
423
|
+
// Drop SettingsList's built-in hint — the panel footer below is the single source.
|
|
424
|
+
while (body.length > 0 && body[body.length - 1] === "") body.pop();
|
|
425
|
+
const listHintIndex = body.findIndex(
|
|
426
|
+
(line) =>
|
|
427
|
+
typeof line === "string" &&
|
|
428
|
+
(line.includes("Enter/Space to change") || line.includes("Esc to cancel")),
|
|
429
|
+
);
|
|
430
|
+
const listBody = listHintIndex >= 0 ? body.slice(0, listHintIndex) : body;
|
|
431
|
+
while (listBody.length > 0 && listBody[listBody.length - 1] === "") listBody.pop();
|
|
432
|
+
|
|
433
|
+
// Frame: top rule · tabs · mid rule · settings · mid rule · footer · bottom rule
|
|
434
|
+
return [
|
|
435
|
+
rule,
|
|
436
|
+
renderSectionTabBar(theme, sections, activeSection, safeWidth),
|
|
437
|
+
rule,
|
|
438
|
+
...listBody,
|
|
439
|
+
rule,
|
|
440
|
+
truncateToWidth(
|
|
441
|
+
theme.fg(
|
|
442
|
+
"dim",
|
|
443
|
+
" Enter/Space to change · Tab/Shift+Tab to switch sections · Esc to close",
|
|
444
|
+
),
|
|
445
|
+
safeWidth,
|
|
446
|
+
),
|
|
447
|
+
rule,
|
|
448
|
+
];
|
|
449
|
+
},
|
|
450
|
+
invalidate() {
|
|
451
|
+
for (const list of lists) list.invalidate();
|
|
452
|
+
},
|
|
453
|
+
handleInput(data: string) {
|
|
454
|
+
if (!excludeSubmenuOpen && isForwardTabKey(data)) {
|
|
455
|
+
switchSection(1);
|
|
456
|
+
tui.requestRender();
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (!excludeSubmenuOpen && isBackTabKey(data)) {
|
|
460
|
+
switchSection(-1);
|
|
461
|
+
tui.requestRender();
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
activeList().handleInput?.(data);
|
|
465
|
+
tui.requestRender();
|
|
466
|
+
},
|
|
467
|
+
};
|
|
468
|
+
});
|
|
469
|
+
}
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
type ExtensionAPI,
|
|
12
12
|
type Theme,
|
|
13
13
|
} from "@earendil-works/pi-coding-agent";
|
|
14
|
-
// @ts-expect-error pi-tui 类型声明的 .ts 后缀 re-export 解析失败;运行时存在
|
|
15
14
|
import { getKeybindings } from "@earendil-works/pi-tui";
|
|
16
15
|
import { Markdown, Spacer, Text, type Component } from "@earendil-works/pi-tui";
|
|
17
16
|
|
|
@@ -48,6 +47,16 @@ type ActiveThinking = {
|
|
|
48
47
|
startedAt: number;
|
|
49
48
|
};
|
|
50
49
|
|
|
50
|
+
// pi-coding-agent 类型声明中 MarkdownTransformer 的 re-export 解析失败,本地用最小结构化类型。
|
|
51
|
+
type MarkdownTransformer = (
|
|
52
|
+
markdown: string,
|
|
53
|
+
context: {
|
|
54
|
+
messageType: "user" | "assistant" | "assistant-thinking";
|
|
55
|
+
isStreaming: boolean;
|
|
56
|
+
availableWidth: number;
|
|
57
|
+
},
|
|
58
|
+
) => string;
|
|
59
|
+
|
|
51
60
|
type AssistantInternals = {
|
|
52
61
|
contentContainer: {
|
|
53
62
|
clear(): void;
|
|
@@ -57,6 +66,8 @@ type AssistantInternals = {
|
|
|
57
66
|
markdownTheme: ConstructorParameters<typeof Markdown>[3];
|
|
58
67
|
hiddenThinkingLabel: string;
|
|
59
68
|
outputPad: number;
|
|
69
|
+
isStreaming: boolean;
|
|
70
|
+
markdownTransformers: readonly MarkdownTransformer[];
|
|
60
71
|
lastMessage?: AssistantMessage;
|
|
61
72
|
hasToolCalls: boolean;
|
|
62
73
|
updateContent(message: AssistantMessage): void;
|
|
@@ -214,6 +225,30 @@ function getLatestOpenAiSummary(thinkingSignature: string | undefined): SummaryP
|
|
|
214
225
|
const WIDGET_ID = "compact-thinking-render-loop";
|
|
215
226
|
|
|
216
227
|
// 上游 index.ts 内联(含 subagent fork patch)。
|
|
228
|
+
function createTransform(
|
|
229
|
+
messageType: "assistant" | "assistant-thinking",
|
|
230
|
+
self: AssistantInternals,
|
|
231
|
+
) {
|
|
232
|
+
// 与 pi 内置 applyMarkdownTransformers 相同的链式语义:
|
|
233
|
+
// 异常保留当前 markdown,继续下一个 transformer。
|
|
234
|
+
return (markdown: string, availableWidth: number): string => {
|
|
235
|
+
let out = markdown;
|
|
236
|
+
for (const transformer of self.markdownTransformers ?? []) {
|
|
237
|
+
try {
|
|
238
|
+
const result = transformer(out, {
|
|
239
|
+
messageType,
|
|
240
|
+
isStreaming: self.isStreaming,
|
|
241
|
+
availableWidth,
|
|
242
|
+
});
|
|
243
|
+
if (typeof result === "string") out = result;
|
|
244
|
+
} catch {
|
|
245
|
+
// 保持当前 markdown 继续
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
217
252
|
function compactThinking(pi: ExtensionAPI) {
|
|
218
253
|
const prototype = AssistantMessageComponent.prototype as PatchedPrototype;
|
|
219
254
|
const originalUpdateContent = prototype.updateContent;
|
|
@@ -328,11 +363,13 @@ function compactThinking(pi: ExtensionAPI) {
|
|
|
328
363
|
const content = message.content[i];
|
|
329
364
|
|
|
330
365
|
if (content.type === "text" && content.text.trim()) {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
366
|
+
self.contentContainer.addChild(
|
|
367
|
+
new Markdown(content.text.trim(), self.outputPad, 0, self.markdownTheme, undefined, {
|
|
368
|
+
transform: createTransform("assistant", self),
|
|
369
|
+
}),
|
|
370
|
+
);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
336
373
|
|
|
337
374
|
if (content.type !== "thinking") continue;
|
|
338
375
|
|