pi-cc-extensions 0.8.30 → 0.8.32
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 +164 -174
- package/README.md +113 -115
- package/extensions/claude-code-style.ts +371 -208
- package/extensions/compact-thinking.ts +58 -0
- package/extensions/fixed-editor.ts +185 -0
- package/extensions/index.ts +10 -4
- package/extensions/subagent-notification.ts +30 -23
- package/node_modules/@tifan/pi-fixed-editor/README.md +36 -0
- package/node_modules/@tifan/pi-fixed-editor/package.json +46 -0
- package/node_modules/@tifan/pi-fixed-editor/src/cluster.ts +113 -0
- package/node_modules/@tifan/pi-fixed-editor/src/index.ts +259 -0
- package/node_modules/@tifan/pi-fixed-editor/src/terminal-split.ts +1337 -0
- package/package.json +9 -5
- package/extensions/ask-user-question/LICENSE +0 -21
- package/extensions/ask-user-question/README.md +0 -63
- package/extensions/ask-user-question/ask-user-question.ts +0 -266
- package/extensions/ask-user-question/config.ts +0 -79
- package/extensions/ask-user-question/events.ts +0 -55
- package/extensions/ask-user-question/index.ts +0 -17
- package/extensions/ask-user-question/reconcile.ts +0 -47
- package/extensions/ask-user-question/rpc-fallback.ts +0 -172
- package/extensions/ask-user-question/state/build-questionnaire.ts +0 -314
- package/extensions/ask-user-question/state/dialog-height.ts +0 -16
- package/extensions/ask-user-question/state/key-router.ts +0 -291
- package/extensions/ask-user-question/state/questionnaire-session.ts +0 -193
- package/extensions/ask-user-question/state/row-intent.ts +0 -143
- package/extensions/ask-user-question/state/selectors/contract.ts +0 -24
- package/extensions/ask-user-question/state/selectors/derivations.ts +0 -40
- package/extensions/ask-user-question/state/selectors/focus.ts +0 -17
- package/extensions/ask-user-question/state/selectors/projections.ts +0 -102
- package/extensions/ask-user-question/state/state-reducer.ts +0 -299
- package/extensions/ask-user-question/state/state.ts +0 -49
- package/extensions/ask-user-question/tool/format-answer.ts +0 -29
- package/extensions/ask-user-question/tool/response-envelope.ts +0 -50
- package/extensions/ask-user-question/tool/types.ts +0 -149
- package/extensions/ask-user-question/tool/validate-questionnaire.ts +0 -64
- package/extensions/ask-user-question/view/component-binding.ts +0 -45
- package/extensions/ask-user-question/view/components/inline-input.ts +0 -109
- package/extensions/ask-user-question/view/components/multi-select-view.ts +0 -198
- package/extensions/ask-user-question/view/components/option-list-view.ts +0 -77
- package/extensions/ask-user-question/view/components/preview/markdown-content-cache.ts +0 -76
- package/extensions/ask-user-question/view/components/preview/preview-block-renderer.ts +0 -114
- package/extensions/ask-user-question/view/components/preview/preview-box-renderer.ts +0 -86
- package/extensions/ask-user-question/view/components/preview/preview-layout-decider.ts +0 -219
- package/extensions/ask-user-question/view/components/preview/preview-pane.ts +0 -230
- package/extensions/ask-user-question/view/components/submit-picker.ts +0 -66
- package/extensions/ask-user-question/view/components/tab-bar.ts +0 -57
- package/extensions/ask-user-question/view/components/wrapping-select.ts +0 -307
- package/extensions/ask-user-question/view/dialog-builder.ts +0 -225
- package/extensions/ask-user-question/view/props-adapter.ts +0 -123
- package/extensions/ask-user-question/view/stateful-view.ts +0 -24
- package/extensions/ask-user-question/view/tab-components.ts +0 -16
- package/extensions/ask-user-question/view/tab-content-strategy.ts +0 -264
|
@@ -5,7 +5,13 @@ import {
|
|
|
5
5
|
type CompactStyleHooks,
|
|
6
6
|
type CompactStyleMode,
|
|
7
7
|
} from "./compact-style.ts";
|
|
8
|
+
import type { CompactThinkingConfig, CompactThinkingController } from "./compact-thinking.ts";
|
|
8
9
|
import { showTextPreview } from "./context.ts";
|
|
10
|
+
import {
|
|
11
|
+
getFixedEditorScrollButtonHitbox,
|
|
12
|
+
installFixedEditor,
|
|
13
|
+
type FixedEditorController,
|
|
14
|
+
} from "./fixed-editor.ts";
|
|
9
15
|
import {
|
|
10
16
|
installToolGrouping,
|
|
11
17
|
ToolGroupComponent,
|
|
@@ -52,9 +58,14 @@ export type Config = {
|
|
|
52
58
|
diffCollapsedLines: number;
|
|
53
59
|
diffWordWrap: boolean;
|
|
54
60
|
expandedPreviewMaxLines: number;
|
|
61
|
+
useSummaryTitlesAsThinkingTitle: boolean;
|
|
62
|
+
previewLines: number;
|
|
63
|
+
animationIntervalMs: number;
|
|
55
64
|
};
|
|
56
65
|
|
|
57
|
-
const
|
|
66
|
+
const AGENT_DIR = process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
|
|
67
|
+
const CONFIG_PATH = join(AGENT_DIR, "claude-code-style.json");
|
|
68
|
+
const LEGACY_COMPACT_THINKING_CONFIG_PATH = join(AGENT_DIR, "compact-thinking.json");
|
|
58
69
|
|
|
59
70
|
const DIFF_VIEW_MODES: DiffViewMode[] = ["auto", "split", "unified"];
|
|
60
71
|
const DIFF_INDICATOR_MODES: DiffIndicatorMode[] = ["bars", "classic", "none"];
|
|
@@ -62,6 +73,8 @@ const DIFF_SPLIT_MIN_WIDTH_VALUES = ["80", "100", "120", "140", "160", "180"];
|
|
|
62
73
|
const DIFF_COLLAPSED_LINES_VALUES = ["12", "24", "36", "48", "80", "120"];
|
|
63
74
|
/** Presets for expanded body height — keep low options first so cycling stays TUI-friendly. */
|
|
64
75
|
const EXPANDED_PREVIEW_MAX_LINES_VALUES = ["40", "60", "80", "120", "200", "500", "2000"];
|
|
76
|
+
const THINKING_PREVIEW_LINES_VALUES = ["0", "1", "3", "5", "10"];
|
|
77
|
+
const THINKING_ANIMATION_INTERVAL_VALUES = ["30", "60", "90", "120", "180"];
|
|
65
78
|
/** Tools commonly toggled in excludeRenderers via the settings panel. */
|
|
66
79
|
const EXCLUDE_RENDERER_CANDIDATES = [
|
|
67
80
|
"bash",
|
|
@@ -85,6 +98,9 @@ export const DEFAULT_CONFIG: Config = {
|
|
|
85
98
|
diffCollapsedLines: DEFAULT_TOOL_DISPLAY_CONFIG.diffCollapsedLines,
|
|
86
99
|
diffWordWrap: DEFAULT_TOOL_DISPLAY_CONFIG.diffWordWrap,
|
|
87
100
|
expandedPreviewMaxLines: DEFAULT_TOOL_DISPLAY_CONFIG.expandedPreviewMaxLines,
|
|
101
|
+
useSummaryTitlesAsThinkingTitle: true,
|
|
102
|
+
previewLines: 3,
|
|
103
|
+
animationIntervalMs: 90,
|
|
88
104
|
};
|
|
89
105
|
|
|
90
106
|
function pickEnum<T extends string>(value: unknown, allowed: readonly T[], fallback: T): T {
|
|
@@ -99,6 +115,11 @@ function pickPositiveInt(value: unknown, fallback: number, min = 1, max = 100_00
|
|
|
99
115
|
return Math.min(max, Math.max(min, Math.floor(n)));
|
|
100
116
|
}
|
|
101
117
|
|
|
118
|
+
function pickPositiveNumber(value: unknown, fallback: number, min = 1): number {
|
|
119
|
+
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
120
|
+
return Number.isFinite(n) ? Math.max(min, n) : fallback;
|
|
121
|
+
}
|
|
122
|
+
|
|
102
123
|
function nearestPreset(value: number, presets: readonly string[]): string {
|
|
103
124
|
const numeric = presets.map((p) => Number(p));
|
|
104
125
|
let best = presets[0] ?? String(value);
|
|
@@ -165,6 +186,25 @@ export function normalizeConfig(input: unknown): Config {
|
|
|
165
186
|
10,
|
|
166
187
|
50_000,
|
|
167
188
|
),
|
|
189
|
+
useSummaryTitlesAsThinkingTitle: source.useSummaryTitlesAsThinkingTitle !== false,
|
|
190
|
+
previewLines: pickPositiveInt(
|
|
191
|
+
source.previewLines,
|
|
192
|
+
DEFAULT_CONFIG.previewLines,
|
|
193
|
+
0,
|
|
194
|
+
Number.MAX_SAFE_INTEGER,
|
|
195
|
+
),
|
|
196
|
+
animationIntervalMs: pickPositiveNumber(
|
|
197
|
+
source.animationIntervalMs,
|
|
198
|
+
DEFAULT_CONFIG.animationIntervalMs,
|
|
199
|
+
),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function getCompactThinkingConfig(source: Config = config): CompactThinkingConfig {
|
|
204
|
+
return {
|
|
205
|
+
useSummaryTitlesAsThinkingTitle: source.useSummaryTitlesAsThinkingTitle,
|
|
206
|
+
previewLines: source.previewLines,
|
|
207
|
+
animationIntervalMs: source.animationIntervalMs,
|
|
168
208
|
};
|
|
169
209
|
}
|
|
170
210
|
|
|
@@ -194,6 +234,9 @@ export function formatConfigStatus(source: Config = config): string {
|
|
|
194
234
|
`diffCollapsed=${source.diffCollapsedLines}`,
|
|
195
235
|
`diffWordWrap=${source.diffWordWrap ? "on" : "off"}`,
|
|
196
236
|
`expandedMax=${source.expandedPreviewMaxLines}`,
|
|
237
|
+
`thinkingTitle=${source.useSummaryTitlesAsThinkingTitle ? "summary" : "default"}`,
|
|
238
|
+
`thinkingPreview=${source.previewLines}`,
|
|
239
|
+
`thinkingAnimation=${source.animationIntervalMs}ms`,
|
|
197
240
|
].join(" · ");
|
|
198
241
|
}
|
|
199
242
|
|
|
@@ -201,26 +244,45 @@ let config: Config = loadConfig();
|
|
|
201
244
|
|
|
202
245
|
function loadConfig(): Config {
|
|
203
246
|
try {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
247
|
+
const source = existsSync(CONFIG_PATH)
|
|
248
|
+
? (JSON.parse(readFileSync(CONFIG_PATH, "utf8")) as Record<string, unknown>)
|
|
249
|
+
: {};
|
|
250
|
+
let migrated = false;
|
|
251
|
+
if (existsSync(LEGACY_COMPACT_THINKING_CONFIG_PATH)) {
|
|
252
|
+
try {
|
|
253
|
+
const legacy = JSON.parse(
|
|
254
|
+
readFileSync(LEGACY_COMPACT_THINKING_CONFIG_PATH, "utf8"),
|
|
255
|
+
) as Record<string, unknown>;
|
|
256
|
+
for (const key of [
|
|
257
|
+
"useSummaryTitlesAsThinkingTitle",
|
|
258
|
+
"previewLines",
|
|
259
|
+
"animationIntervalMs",
|
|
260
|
+
] as const) {
|
|
261
|
+
if (!(key in source) && key in legacy) {
|
|
262
|
+
source[key] = legacy[key];
|
|
263
|
+
migrated = true;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
} catch {
|
|
267
|
+
// A malformed legacy file must not invalidate the ccstyle source.
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const normalized = normalizeConfig(source);
|
|
271
|
+
// Persist one-time migrations while retaining existing ccstyle values.
|
|
272
|
+
if (
|
|
273
|
+
migrated ||
|
|
274
|
+
(typeof source.enabled === "boolean" &&
|
|
212
275
|
source.mode !== "on" &&
|
|
213
276
|
source.mode !== "off" &&
|
|
214
|
-
source.mode !== "compact"
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
277
|
+
source.mode !== "compact")
|
|
278
|
+
) {
|
|
279
|
+
try {
|
|
280
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(normalized, null, 2));
|
|
281
|
+
} catch {
|
|
282
|
+
// A read-only config still uses the migrated in-memory value.
|
|
221
283
|
}
|
|
222
|
-
return normalized;
|
|
223
284
|
}
|
|
285
|
+
return normalized;
|
|
224
286
|
} catch {
|
|
225
287
|
// Ignore bad config and fall back to defaults.
|
|
226
288
|
}
|
|
@@ -541,6 +603,7 @@ export class ExpandedToolIoView {
|
|
|
541
603
|
}
|
|
542
604
|
|
|
543
605
|
render(width: number): string[] {
|
|
606
|
+
recordRenderedIoView(this);
|
|
544
607
|
if (this.cachedLines !== undefined && this.cachedWidth === width) return this.cachedLines;
|
|
545
608
|
|
|
546
609
|
const theme = this.theme;
|
|
@@ -861,6 +924,8 @@ type ToolRenderHit = {
|
|
|
861
924
|
row: number;
|
|
862
925
|
};
|
|
863
926
|
|
|
927
|
+
type FrameToolRender = { component: any; lines: string[] };
|
|
928
|
+
|
|
864
929
|
const TOOL_MOUSE_WIDGET_KEY = "ccstyle-tool-mouse";
|
|
865
930
|
const TOOL_MOUSE_ENABLE = "\x1b[?1000h\x1b[?1003h\x1b[?1006h";
|
|
866
931
|
const TOOL_MOUSE_MOTION_ENABLE = "\x1b[?1003h\x1b[?1006h";
|
|
@@ -883,8 +948,10 @@ let toolMouseInputPatchWrapper: ((...args: any[]) => any) | null = null;
|
|
|
883
948
|
let toolMouseRenderPatchTui: any = null;
|
|
884
949
|
let toolMouseRenderPatchOriginal: ((...args: any[]) => any) | null = null;
|
|
885
950
|
let toolMouseRenderPatchWrapper: ((...args: any[]) => any) | null = null;
|
|
951
|
+
let toolMouseRenderPatchState: { active: boolean } | null = null;
|
|
886
952
|
let toolMouseRawWrite: ((data: string) => unknown) | null = null;
|
|
887
953
|
let scrollButtonVisible = false;
|
|
954
|
+
let scrollButtonHovered = false;
|
|
888
955
|
let scrollButtonWidget: any = null;
|
|
889
956
|
let pendingScrollMessages = 0;
|
|
890
957
|
let assistantMessageActive = false;
|
|
@@ -892,6 +959,7 @@ let scrollButtonSyncScheduled = false;
|
|
|
892
959
|
let sessionRenderTimer: ReturnType<typeof setTimeout> | null = null;
|
|
893
960
|
let hoveredToolCallId: string | null = null;
|
|
894
961
|
let hoveredToolGroup: ToolGroupComponent | null = null;
|
|
962
|
+
let frameToolRenders: FrameToolRender[] = [];
|
|
895
963
|
|
|
896
964
|
function parseSgrMousePackets(data: string): SgrMousePacket[] | null {
|
|
897
965
|
const pattern = /\x1b\[<(\d+);(\d+);(\d+)([Mm])/g;
|
|
@@ -1018,8 +1086,6 @@ function fixedEditorContextScore(
|
|
|
1018
1086
|
|
|
1019
1087
|
/** Summary markers used by Pi and ccstyle; unlike the trailing hint, these survive truncation. */
|
|
1020
1088
|
const COLLAPSED_TOOL_SUMMARY = /^\s*(?:↳|└|⎿|●|✓|✗|…)/;
|
|
1021
|
-
/** fixedEditorContextScore max ≈ 100 + 2*(5+4+3+2); stop once we hit a near-perfect match. */
|
|
1022
|
-
const TOOL_CLICK_EARLY_EXIT_SCORE = 120;
|
|
1023
1089
|
|
|
1024
1090
|
function findToolAtFixedEditorRow(
|
|
1025
1091
|
tui: any,
|
|
@@ -1034,20 +1100,25 @@ function findToolAtFixedEditorRow(
|
|
|
1034
1100
|
const hasBackground = /\x1b\[(?:4[0-8]|10[0-7])(?:;[0-9;:]*)?m/.test(rawClickedLine);
|
|
1035
1101
|
if (!clickedLine && !hasBackground) return null;
|
|
1036
1102
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1103
|
+
let candidates = frameToolRenders;
|
|
1104
|
+
if (candidates.length === 0) {
|
|
1105
|
+
const tools: any[] = [];
|
|
1106
|
+
collectToolComponents(tui, tools);
|
|
1107
|
+
candidates = tools.map((component) => ({
|
|
1108
|
+
component,
|
|
1109
|
+
lines: renderComponentTree(component, width),
|
|
1110
|
+
}));
|
|
1111
|
+
}
|
|
1039
1112
|
const clickedCollapsedSummary = COLLAPSED_TOOL_SUMMARY.test(clickedLine);
|
|
1040
1113
|
let best: { hit: ToolRenderHit; score: number } | null = null;
|
|
1041
|
-
for (const component of
|
|
1114
|
+
for (const { component, lines } of candidates) {
|
|
1042
1115
|
// Collapsed tools open only from their summary row; expanded tools collapse from any body row.
|
|
1043
1116
|
if (!component.expanded && !clickedCollapsedSummary) continue;
|
|
1044
|
-
const renderedLines =
|
|
1045
|
-
stripTerminalSequences(String(line)),
|
|
1046
|
-
);
|
|
1117
|
+
const renderedLines = lines.map((line) => stripTerminalSequences(String(line)));
|
|
1047
1118
|
for (let renderedRow = 0; renderedRow < renderedLines.length; renderedRow++) {
|
|
1048
1119
|
if (!fixedEditorLineMatch(renderedLines[renderedRow] ?? "", clickedLine)) continue;
|
|
1049
1120
|
const score = fixedEditorContextScore(renderedLines, renderedRow, visibleLines, visibleRow);
|
|
1050
|
-
if (!best || score
|
|
1121
|
+
if (!best || score >= best.score) {
|
|
1051
1122
|
best = {
|
|
1052
1123
|
hit: {
|
|
1053
1124
|
component,
|
|
@@ -1057,7 +1128,6 @@ function findToolAtFixedEditorRow(
|
|
|
1057
1128
|
},
|
|
1058
1129
|
score,
|
|
1059
1130
|
};
|
|
1060
|
-
if (score >= TOOL_CLICK_EARLY_EXIT_SCORE) return best.hit;
|
|
1061
1131
|
}
|
|
1062
1132
|
}
|
|
1063
1133
|
}
|
|
@@ -1220,8 +1290,9 @@ function isFixedEditorAtBottom(tui: any): boolean {
|
|
|
1220
1290
|
}
|
|
1221
1291
|
|
|
1222
1292
|
function hideScrollButton(tui: any): void {
|
|
1223
|
-
const changed = scrollButtonVisible || pendingScrollMessages > 0;
|
|
1293
|
+
const changed = scrollButtonVisible || scrollButtonHovered || pendingScrollMessages > 0;
|
|
1224
1294
|
scrollButtonVisible = false;
|
|
1295
|
+
scrollButtonHovered = false;
|
|
1225
1296
|
pendingScrollMessages = 0;
|
|
1226
1297
|
if (changed) tui.requestRender?.();
|
|
1227
1298
|
}
|
|
@@ -1257,9 +1328,7 @@ function scheduleScrollButtonSync(tui: any, data: string): void {
|
|
|
1257
1328
|
|
|
1258
1329
|
function updateScrollButtonFromInput(tui: any, data: string): void {
|
|
1259
1330
|
if (!useFixedEditorFeatures(tui)) return;
|
|
1260
|
-
if (matchesKey(data, "enter") || matchesKey(data, "return")
|
|
1261
|
-
hideScrollButton(tui);
|
|
1262
|
-
}
|
|
1331
|
+
if (matchesKey(data, "enter") || matchesKey(data, "return")) hideScrollButton(tui);
|
|
1263
1332
|
}
|
|
1264
1333
|
|
|
1265
1334
|
function renderComponentTree(component: any, width: number): string[] {
|
|
@@ -1274,58 +1343,6 @@ function renderComponentTree(component: any, width: number): string[] {
|
|
|
1274
1343
|
return component.children.flatMap((child: any) => renderComponentTree(child, width));
|
|
1275
1344
|
}
|
|
1276
1345
|
|
|
1277
|
-
function renderTreeWithTarget(
|
|
1278
|
-
component: any,
|
|
1279
|
-
target: any,
|
|
1280
|
-
width: number,
|
|
1281
|
-
seen = new Set<any>(),
|
|
1282
|
-
): { lines: string[]; targetStart: number | null } {
|
|
1283
|
-
if (!component || typeof component !== "object" || seen.has(component)) {
|
|
1284
|
-
return { lines: [], targetStart: null };
|
|
1285
|
-
}
|
|
1286
|
-
seen.add(component);
|
|
1287
|
-
if (component === target) {
|
|
1288
|
-
return { lines: renderComponentTree(component, width), targetStart: 0 };
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
if (Array.isArray(component.children)) {
|
|
1292
|
-
const lines: string[] = [];
|
|
1293
|
-
let targetStart: number | null = null;
|
|
1294
|
-
for (const child of component.children) {
|
|
1295
|
-
const result = renderTreeWithTarget(child, target, width, seen);
|
|
1296
|
-
if (result.targetStart !== null) targetStart = lines.length + result.targetStart;
|
|
1297
|
-
lines.push(...result.lines);
|
|
1298
|
-
}
|
|
1299
|
-
if (targetStart !== null) return { lines, targetStart };
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
return { lines: renderComponentTree(component, width), targetStart: null };
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
function normalizedClusterLines(component: any, width: number): string[] {
|
|
1306
|
-
if (!component) return [];
|
|
1307
|
-
const lines = renderComponentTree(component, width);
|
|
1308
|
-
let end = lines.length;
|
|
1309
|
-
while (end > 0 && visibleWidth(lines[end - 1] ?? "") === 0) end--;
|
|
1310
|
-
return lines.slice(0, Math.max(end, 1));
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
function rawTerminalRows(tui: any): number {
|
|
1314
|
-
const terminal = tui?.terminal;
|
|
1315
|
-
if (!terminal) return 0;
|
|
1316
|
-
const prototype = Object.getPrototypeOf(terminal);
|
|
1317
|
-
const rows = prototype ? Object.getOwnPropertyDescriptor(prototype, "rows") : undefined;
|
|
1318
|
-
if (typeof rows?.get === "function") {
|
|
1319
|
-
try {
|
|
1320
|
-
const value = rows.get.call(terminal);
|
|
1321
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1322
|
-
} catch {
|
|
1323
|
-
// Fall through to the current terminal value.
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
return typeof terminal.rows === "number" && Number.isFinite(terminal.rows) ? terminal.rows : 0;
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
1346
|
function containsEditorLike(component: any, focused: any, seen = new Set<any>()): boolean {
|
|
1330
1347
|
if (!component || typeof component !== "object" || seen.has(component)) return false;
|
|
1331
1348
|
seen.add(component);
|
|
@@ -1342,70 +1359,15 @@ function containsEditorLike(component: any, focused: any, seen = new Set<any>())
|
|
|
1342
1359
|
);
|
|
1343
1360
|
}
|
|
1344
1361
|
|
|
1345
|
-
function scrollButtonScreenRow(tui: any, width: number): number | null {
|
|
1346
|
-
if (!scrollButtonVisible || !useFixedEditorFeatures(tui) || !scrollButtonWidget) return null;
|
|
1347
|
-
const children = Array.isArray(tui?.children) ? tui.children : [];
|
|
1348
|
-
const editorIndex = children.findIndex((child: any) =>
|
|
1349
|
-
containsEditorLike(child, tui.focusedComponent),
|
|
1350
|
-
);
|
|
1351
|
-
if (editorIndex < 2 || editorIndex + 2 >= children.length) return null;
|
|
1352
|
-
|
|
1353
|
-
const above = children[editorIndex - 1];
|
|
1354
|
-
const widthValue = Math.max(1, width || Number(tui?.terminal?.columns) || 80);
|
|
1355
|
-
const target = renderTreeWithTarget(above, scrollButtonWidget, widthValue);
|
|
1356
|
-
if (target.targetStart === null) return null;
|
|
1357
|
-
|
|
1358
|
-
const rawRows = rawTerminalRows(tui);
|
|
1359
|
-
if (rawRows <= 0) return null;
|
|
1360
|
-
const maxRows = Math.max(1, rawRows - 1);
|
|
1361
|
-
const status = normalizedClusterLines(children[editorIndex - 2], widthValue);
|
|
1362
|
-
const editor = normalizedClusterLines(children[editorIndex], widthValue);
|
|
1363
|
-
const below = normalizedClusterLines(children[editorIndex + 1], widthValue);
|
|
1364
|
-
const footer = normalizedClusterLines(children[editorIndex + 2], widthValue);
|
|
1365
|
-
const aboveLines =
|
|
1366
|
-
target.lines.length > 0 ? target.lines : normalizedClusterLines(above, widthValue);
|
|
1367
|
-
|
|
1368
|
-
const takeLast = (lines: string[], count: number): string[] =>
|
|
1369
|
-
count > 0 ? lines.slice(-count) : [];
|
|
1370
|
-
const editorVisible = takeLast(editor, Math.min(editor.length, maxRows));
|
|
1371
|
-
let remaining = Math.max(0, maxRows - editorVisible.length);
|
|
1372
|
-
const footerVisible = takeLast(footer, remaining);
|
|
1373
|
-
remaining -= footerVisible.length;
|
|
1374
|
-
const belowVisible = takeLast(below, remaining);
|
|
1375
|
-
remaining -= belowVisible.length;
|
|
1376
|
-
const aboveVisible = takeLast(aboveLines, remaining);
|
|
1377
|
-
const statusVisible = takeLast(status, Math.max(0, remaining - aboveVisible.length));
|
|
1378
|
-
const aboveStart = aboveLines.length - aboveVisible.length;
|
|
1379
|
-
const targetRow = target.targetStart - aboveStart;
|
|
1380
|
-
if (targetRow < 0 || targetRow >= aboveVisible.length) return null;
|
|
1381
|
-
|
|
1382
|
-
const allLines = [
|
|
1383
|
-
...statusVisible,
|
|
1384
|
-
...aboveVisible,
|
|
1385
|
-
...editorVisible,
|
|
1386
|
-
...belowVisible,
|
|
1387
|
-
...footerVisible,
|
|
1388
|
-
];
|
|
1389
|
-
let leadingBlank = 0;
|
|
1390
|
-
while (leadingBlank < allLines.length - 1 && visibleWidth(allLines[leadingBlank] ?? "") === 0) {
|
|
1391
|
-
leadingBlank++;
|
|
1392
|
-
}
|
|
1393
|
-
const clusterRow = statusVisible.length + targetRow - leadingBlank;
|
|
1394
|
-
if (clusterRow < 0 || clusterRow >= allLines.length - leadingBlank) return null;
|
|
1395
|
-
return rawRows - allLines.length + clusterRow + 1;
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
1362
|
function isScrollButtonAtScreenRow(tui: any, packet: SgrMousePacket): boolean {
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
const end = visibleWidth(plain.trimEnd());
|
|
1408
|
-
return packet.col >= leading + 1 && packet.col <= end;
|
|
1363
|
+
if (!scrollButtonVisible || !useFixedEditorFeatures(tui) || !scrollButtonWidget) return false;
|
|
1364
|
+
const hitbox = getFixedEditorScrollButtonHitbox();
|
|
1365
|
+
return Boolean(
|
|
1366
|
+
hitbox &&
|
|
1367
|
+
packet.row === hitbox.row &&
|
|
1368
|
+
packet.col >= hitbox.startCol &&
|
|
1369
|
+
packet.col <= hitbox.endCol,
|
|
1370
|
+
);
|
|
1409
1371
|
}
|
|
1410
1372
|
|
|
1411
1373
|
function jumpToBottomWithoutSubmit(tui: any): boolean {
|
|
@@ -1454,11 +1416,19 @@ function scheduleCollapseViewportCompensation(
|
|
|
1454
1416
|
|
|
1455
1417
|
/** toolCallId → latest expanded IO view (survives context/state identity quirks). */
|
|
1456
1418
|
const ioViewsByToolCallId = new Map<string, ExpandedToolIoView>();
|
|
1419
|
+
const ioViewInvalidators = new WeakMap<ExpandedToolIoView, () => void>();
|
|
1420
|
+
const ioViewCollectors = new Set<Set<ExpandedToolIoView>>();
|
|
1421
|
+
let frameIoViews = new Set<ExpandedToolIoView>();
|
|
1457
1422
|
let hoveredToolIoView: ExpandedToolIoView | null = null;
|
|
1458
1423
|
let hoveredToolIoSection: ToolIoSection | null = null;
|
|
1459
1424
|
|
|
1425
|
+
function recordRenderedIoView(view: ExpandedToolIoView): void {
|
|
1426
|
+
for (const collector of ioViewCollectors) collector.add(view);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1460
1429
|
function rememberIoView(context: any, view: ExpandedToolIoView): void {
|
|
1461
1430
|
if (!context || typeof context !== "object") return;
|
|
1431
|
+
if (typeof context.invalidate === "function") ioViewInvalidators.set(view, context.invalidate);
|
|
1462
1432
|
if (!context.state || typeof context.state !== "object") context.state = {};
|
|
1463
1433
|
const state = context.state as Record<string, unknown>;
|
|
1464
1434
|
state.ccstyleIoView = view;
|
|
@@ -1494,6 +1464,50 @@ function resolveIoViewFromTool(component: any): ExpandedToolIoView | null {
|
|
|
1494
1464
|
return null;
|
|
1495
1465
|
}
|
|
1496
1466
|
|
|
1467
|
+
function resolveShowMoreAt(
|
|
1468
|
+
tui: any,
|
|
1469
|
+
packet: SgrMousePacket,
|
|
1470
|
+
component?: any,
|
|
1471
|
+
): { view: ExpandedToolIoView; section: ToolIoSection } | null {
|
|
1472
|
+
const plain = visibleMouseLine(tui, packet);
|
|
1473
|
+
const resolved = resolveIoViewFromTool(component);
|
|
1474
|
+
const renderedViews =
|
|
1475
|
+
frameIoViews.size > 0
|
|
1476
|
+
? [...frameIoViews]
|
|
1477
|
+
: [
|
|
1478
|
+
...(resolved ? [resolved] : []),
|
|
1479
|
+
...[...ioViewsByToolCallId.values()].filter((view) => view !== resolved),
|
|
1480
|
+
];
|
|
1481
|
+
const preferred = resolved && renderedViews.includes(resolved) ? resolved : null;
|
|
1482
|
+
const candidates = preferred
|
|
1483
|
+
? [preferred, ...renderedViews.filter((view) => view !== preferred)]
|
|
1484
|
+
: renderedViews;
|
|
1485
|
+
const previousLines = (Array.isArray(tui?.previousLines) ? tui.previousLines : []).map(
|
|
1486
|
+
(line: unknown) => stripTerminalSequencesPreservingLayout(String(line)),
|
|
1487
|
+
);
|
|
1488
|
+
const visibleRow = useFixedEditorFeatures(tui)
|
|
1489
|
+
? packet.row - 1
|
|
1490
|
+
: (Number.isFinite(tui?.previousViewportTop) ? tui.previousViewportTop : 0) + packet.row - 1;
|
|
1491
|
+
let best: { view: ExpandedToolIoView; section: ToolIoSection; score: number } | null = null;
|
|
1492
|
+
for (const view of candidates) {
|
|
1493
|
+
const section = view.matchShowMoreLine(plain);
|
|
1494
|
+
const box = view.showMoreHitbox(plain);
|
|
1495
|
+
if (!section || !box || packet.col < box.startCol || packet.col > box.endCol) continue;
|
|
1496
|
+
const cachedLines = Array.isArray((view as any).cachedLines)
|
|
1497
|
+
? (view as any).cachedLines.map((line: unknown) =>
|
|
1498
|
+
stripTerminalSequencesPreservingLayout(String(line)),
|
|
1499
|
+
)
|
|
1500
|
+
: [];
|
|
1501
|
+
let score = view === preferred ? 1 : 0;
|
|
1502
|
+
for (let row = 0; row < cachedLines.length; row++) {
|
|
1503
|
+
if (!fixedEditorLineMatch(cachedLines[row] ?? "", plain)) continue;
|
|
1504
|
+
score = Math.max(score, fixedEditorContextScore(cachedLines, row, previousLines, visibleRow));
|
|
1505
|
+
}
|
|
1506
|
+
if (!best || score > best.score) best = { view, section, score };
|
|
1507
|
+
}
|
|
1508
|
+
return best && { view: best.view, section: best.section };
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1497
1511
|
/**
|
|
1498
1512
|
* If the click lands on a truncated section's [show more], open the /context-style
|
|
1499
1513
|
* full-text preview instead of toggling expand/collapse.
|
|
@@ -1520,21 +1534,9 @@ function isCollapsedHintAtColumn(line: string, col: number): boolean {
|
|
|
1520
1534
|
|
|
1521
1535
|
function tryOpenToolIoShowMore(tui: any, packet: SgrMousePacket, hit: ToolRenderHit): boolean {
|
|
1522
1536
|
if (!Boolean(hit.component.expanded)) return false;
|
|
1523
|
-
const
|
|
1524
|
-
if (!
|
|
1525
|
-
|
|
1526
|
-
const previousLines = Array.isArray(tui?.previousLines) ? tui.previousLines : [];
|
|
1527
|
-
const visibleRow = useFixedEditorFeatures(tui)
|
|
1528
|
-
? packet.row - 1
|
|
1529
|
-
: (Number.isFinite(tui?.previousViewportTop) ? tui.previousViewportTop : 0) + packet.row - 1;
|
|
1530
|
-
if (visibleRow < 0 || visibleRow >= previousLines.length) return false;
|
|
1531
|
-
|
|
1532
|
-
const plain = stripTerminalSequencesPreservingLayout(String(previousLines[visibleRow] ?? ""));
|
|
1533
|
-
const section = ioView.matchShowMoreLine(plain);
|
|
1534
|
-
if (!section) return false;
|
|
1535
|
-
|
|
1536
|
-
const box = ioView.showMoreHitbox(plain);
|
|
1537
|
-
if (!box || packet.col < box.startCol || packet.col > box.endCol) return false;
|
|
1537
|
+
const match = resolveShowMoreAt(tui, packet, hit.component);
|
|
1538
|
+
if (!match) return false;
|
|
1539
|
+
const { view: ioView, section } = match;
|
|
1538
1540
|
|
|
1539
1541
|
const ui = toolMouseUi;
|
|
1540
1542
|
if (!ui || typeof ui.custom !== "function") {
|
|
@@ -1551,10 +1553,16 @@ function tryOpenToolIoShowMore(tui: any, packet: SgrMousePacket, hit: ToolRender
|
|
|
1551
1553
|
|
|
1552
1554
|
function setHoveredToolIo(view: ExpandedToolIoView | null, section: ToolIoSection | null): boolean {
|
|
1553
1555
|
if (view === hoveredToolIoView && section === hoveredToolIoSection) return false;
|
|
1554
|
-
hoveredToolIoView
|
|
1556
|
+
if (hoveredToolIoView) {
|
|
1557
|
+
hoveredToolIoView.setHoveredSection(null);
|
|
1558
|
+
ioViewInvalidators.get(hoveredToolIoView)?.();
|
|
1559
|
+
}
|
|
1555
1560
|
hoveredToolIoView = view;
|
|
1556
1561
|
hoveredToolIoSection = section;
|
|
1557
|
-
view
|
|
1562
|
+
if (view) {
|
|
1563
|
+
view.setHoveredSection(section);
|
|
1564
|
+
ioViewInvalidators.get(view)?.();
|
|
1565
|
+
}
|
|
1558
1566
|
return true;
|
|
1559
1567
|
}
|
|
1560
1568
|
|
|
@@ -1568,6 +1576,9 @@ function setHoveredToolGroup(group: ToolGroupComponent | null): boolean {
|
|
|
1568
1576
|
|
|
1569
1577
|
function updateToolSummaryHover(tui: any, packet: SgrMousePacket): void {
|
|
1570
1578
|
if ((packet.code & 32) === 0 || packet.final !== "M") return;
|
|
1579
|
+
const nextScrollButtonHovered = isScrollButtonAtScreenRow(tui, packet);
|
|
1580
|
+
const scrollButtonChanged = nextScrollButtonHovered !== scrollButtonHovered;
|
|
1581
|
+
scrollButtonHovered = nextScrollButtonHovered;
|
|
1571
1582
|
const visibleLine = visibleMouseLine(tui, packet);
|
|
1572
1583
|
let nextToolCallId: string | null = null;
|
|
1573
1584
|
let nextGroup: ToolGroupComponent | null = null;
|
|
@@ -1583,18 +1594,21 @@ function updateToolSummaryHover(tui: any, packet: SgrMousePacket): void {
|
|
|
1583
1594
|
nextGroup = hit?.component instanceof ToolGroupComponent ? hit.component : null;
|
|
1584
1595
|
} else if (visibleLine.includes(SHOW_MORE_LABEL)) {
|
|
1585
1596
|
const hit = findToolAtScreenRow(tui, packet.row);
|
|
1586
|
-
const
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
nextIoView = view;
|
|
1591
|
-
nextIoSection = section;
|
|
1597
|
+
const match = resolveShowMoreAt(tui, packet, hit?.component);
|
|
1598
|
+
if (match) {
|
|
1599
|
+
nextIoView = match.view;
|
|
1600
|
+
nextIoSection = match.section;
|
|
1592
1601
|
}
|
|
1593
1602
|
}
|
|
1594
1603
|
|
|
1595
1604
|
const changed = nextToolCallId !== hoveredToolCallId;
|
|
1596
1605
|
hoveredToolCallId = nextToolCallId;
|
|
1597
|
-
if (
|
|
1606
|
+
if (
|
|
1607
|
+
scrollButtonChanged ||
|
|
1608
|
+
setHoveredToolIo(nextIoView, nextIoSection) ||
|
|
1609
|
+
setHoveredToolGroup(nextGroup) ||
|
|
1610
|
+
changed
|
|
1611
|
+
)
|
|
1598
1612
|
tui.requestRender?.();
|
|
1599
1613
|
}
|
|
1600
1614
|
|
|
@@ -1652,7 +1666,10 @@ function renderScrollButton(width: number, theme: any): string[] {
|
|
|
1652
1666
|
pendingScrollMessages > 0
|
|
1653
1667
|
? `${pendingScrollMessages} new message${pendingScrollMessages === 1 ? "" : "s"}`
|
|
1654
1668
|
: "Back to bottom";
|
|
1655
|
-
const label = theme.fg(
|
|
1669
|
+
const label = theme.fg(
|
|
1670
|
+
scrollButtonHovered ? "text" : "accent",
|
|
1671
|
+
`[ ↓ ${messageText} · ${shortcut} ]`,
|
|
1672
|
+
);
|
|
1656
1673
|
const leftPad = Math.max(0, Math.floor((width - visibleWidth(label)) / 2));
|
|
1657
1674
|
return [`${" ".repeat(leftPad)}${truncateToWidth(label, width, "…")}`];
|
|
1658
1675
|
}
|
|
@@ -1728,6 +1745,7 @@ function restoreToolMouseInputCapture(): void {
|
|
|
1728
1745
|
}
|
|
1729
1746
|
|
|
1730
1747
|
function restoreToolMouseRenderPatch(): void {
|
|
1748
|
+
if (toolMouseRenderPatchState) toolMouseRenderPatchState.active = false;
|
|
1731
1749
|
if (
|
|
1732
1750
|
toolMouseRenderPatchTui &&
|
|
1733
1751
|
toolMouseRenderPatchOriginal &&
|
|
@@ -1738,7 +1756,11 @@ function restoreToolMouseRenderPatch(): void {
|
|
|
1738
1756
|
toolMouseRenderPatchTui = null;
|
|
1739
1757
|
toolMouseRenderPatchOriginal = null;
|
|
1740
1758
|
toolMouseRenderPatchWrapper = null;
|
|
1759
|
+
toolMouseRenderPatchState = null;
|
|
1741
1760
|
toolMouseRawWrite = null;
|
|
1761
|
+
ioViewCollectors.clear();
|
|
1762
|
+
frameIoViews = new Set();
|
|
1763
|
+
frameToolRenders = [];
|
|
1742
1764
|
}
|
|
1743
1765
|
|
|
1744
1766
|
function patchToolMouseMotionAfterRender(tui: any): void {
|
|
@@ -1756,10 +1778,63 @@ function patchToolMouseMotionAfterRender(tui: any): void {
|
|
|
1756
1778
|
if (typeof original !== "function" || !rawWrite) return;
|
|
1757
1779
|
|
|
1758
1780
|
toolMouseRawWrite = (data) => Reflect.apply(rawWrite, terminal, [data]);
|
|
1781
|
+
const patchState = { active: true };
|
|
1759
1782
|
const wrapper = function (this: any, ...args: any[]) {
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1783
|
+
if (!patchState.active) return Reflect.apply(original, this, args);
|
|
1784
|
+
const renderedViews = new Set<ExpandedToolIoView>();
|
|
1785
|
+
const renderedTools: FrameToolRender[] = [];
|
|
1786
|
+
const tools: any[] = [];
|
|
1787
|
+
const restores: Array<{ component: any; descriptor?: PropertyDescriptor }> = [];
|
|
1788
|
+
collectToolComponents(this, tools);
|
|
1789
|
+
for (const component of tools) {
|
|
1790
|
+
const descriptor = Object.getOwnPropertyDescriptor(component, "render");
|
|
1791
|
+
const originalRender = component.render;
|
|
1792
|
+
if (typeof originalRender !== "function") continue;
|
|
1793
|
+
const wrappedRender = function (this: any, ...renderArgs: any[]) {
|
|
1794
|
+
const lines = Reflect.apply(originalRender, this, renderArgs);
|
|
1795
|
+
if (Array.isArray(lines)) renderedTools.push({ component, lines: [...lines] });
|
|
1796
|
+
return lines;
|
|
1797
|
+
};
|
|
1798
|
+
try {
|
|
1799
|
+
Object.defineProperty(
|
|
1800
|
+
component,
|
|
1801
|
+
"render",
|
|
1802
|
+
descriptor && "value" in descriptor
|
|
1803
|
+
? { ...descriptor, value: wrappedRender }
|
|
1804
|
+
: {
|
|
1805
|
+
configurable: true,
|
|
1806
|
+
enumerable: descriptor?.enumerable ?? false,
|
|
1807
|
+
writable: true,
|
|
1808
|
+
value: wrappedRender,
|
|
1809
|
+
},
|
|
1810
|
+
);
|
|
1811
|
+
restores.push({ component, descriptor });
|
|
1812
|
+
} catch {
|
|
1813
|
+
// Non-configurable render accessors cannot be observed safely.
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
ioViewCollectors.add(renderedViews);
|
|
1817
|
+
let succeeded = false;
|
|
1818
|
+
try {
|
|
1819
|
+
const result = Reflect.apply(original, this, args);
|
|
1820
|
+
succeeded = true;
|
|
1821
|
+
toolMouseRawWrite?.(TOOL_MOUSE_MOTION_ENABLE);
|
|
1822
|
+
return result;
|
|
1823
|
+
} finally {
|
|
1824
|
+
for (const { component, descriptor } of restores.reverse()) {
|
|
1825
|
+
try {
|
|
1826
|
+
if (descriptor) Object.defineProperty(component, "render", descriptor);
|
|
1827
|
+
else delete component.render;
|
|
1828
|
+
} catch {
|
|
1829
|
+
// Keep restoring the remaining instances after a hostile descriptor change.
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
if (succeeded) {
|
|
1833
|
+
frameIoViews = renderedViews;
|
|
1834
|
+
frameToolRenders = renderedTools;
|
|
1835
|
+
}
|
|
1836
|
+
ioViewCollectors.delete(renderedViews);
|
|
1837
|
+
}
|
|
1763
1838
|
};
|
|
1764
1839
|
try {
|
|
1765
1840
|
tui.doRender = wrapper;
|
|
@@ -1770,6 +1845,7 @@ function patchToolMouseMotionAfterRender(tui: any): void {
|
|
|
1770
1845
|
toolMouseRenderPatchTui = tui;
|
|
1771
1846
|
toolMouseRenderPatchOriginal = original;
|
|
1772
1847
|
toolMouseRenderPatchWrapper = wrapper;
|
|
1848
|
+
toolMouseRenderPatchState = patchState;
|
|
1773
1849
|
toolMouseRawWrite(TOOL_MOUSE_MOTION_ENABLE);
|
|
1774
1850
|
}
|
|
1775
1851
|
|
|
@@ -1841,6 +1917,7 @@ function teardownToolMouseInteraction(): void {
|
|
|
1841
1917
|
restoreToolMouseInputCapture();
|
|
1842
1918
|
restoreToolMouseRenderPatch();
|
|
1843
1919
|
scrollButtonVisible = false;
|
|
1920
|
+
scrollButtonHovered = false;
|
|
1844
1921
|
scrollButtonWidget = null;
|
|
1845
1922
|
pendingScrollMessages = 0;
|
|
1846
1923
|
assistantMessageActive = false;
|
|
@@ -1866,7 +1943,10 @@ export function installToolMouseInteraction(
|
|
|
1866
1943
|
toolMouseFixedFeaturesEnabled = fixedEditorFeatures;
|
|
1867
1944
|
ctx.ui.setWidget(TOOL_MOUSE_WIDGET_KEY, (tui: any, theme: any) => {
|
|
1868
1945
|
toolMouseTui = tui;
|
|
1869
|
-
if (fixedEditorFeatures)
|
|
1946
|
+
if (fixedEditorFeatures) {
|
|
1947
|
+
patchToolMouseInputCapture(tui);
|
|
1948
|
+
patchToolMouseMotionAfterRender(tui);
|
|
1949
|
+
}
|
|
1870
1950
|
// Motion reporting is required for hover in both native and fixed-editor layouts.
|
|
1871
1951
|
tui?.terminal?.write?.(fixedEditorFeatures ? TOOL_MOUSE_ENABLE : TOOL_MOUSE_MOTION_ENABLE);
|
|
1872
1952
|
const widget = {
|
|
@@ -1941,8 +2021,8 @@ function modeSettingDescription(mode: CompactStyleMode): string {
|
|
|
1941
2021
|
|
|
1942
2022
|
function fixedEditorSettingDescription(enabled: boolean): string {
|
|
1943
2023
|
return enabled
|
|
1944
|
-
? "Mouse capture, 5-row wheel, tool
|
|
1945
|
-
: "
|
|
2024
|
+
? "Pinned editor via @tifan/pi-fixed-editor. Mouse capture, 5-row wheel, tool clicks, back-to-bottom button, message count, and Ctrl+End."
|
|
2025
|
+
: "Native scrolling editor; fixed-editor mouse features and button off. Ctrl+End remains enabled.";
|
|
1946
2026
|
}
|
|
1947
2027
|
|
|
1948
2028
|
function excludeRenderersDescription(names: readonly string[]): string {
|
|
@@ -2013,7 +2093,7 @@ function buildExcludeRenderersSubmenu(
|
|
|
2013
2093
|
|
|
2014
2094
|
/** Section tabs for /ccstyle — matches Zentui-style "A / B / C" headers. */
|
|
2015
2095
|
type CcstyleSection = {
|
|
2016
|
-
id: "style" | "editor" | "diff";
|
|
2096
|
+
id: "style" | "editor" | "diff" | "thinking";
|
|
2017
2097
|
label: string;
|
|
2018
2098
|
items: any[];
|
|
2019
2099
|
};
|
|
@@ -2053,7 +2133,9 @@ function renderSectionTabBar(
|
|
|
2053
2133
|
async function showCcstylePanel(
|
|
2054
2134
|
ctx: any,
|
|
2055
2135
|
compactStyle: CompactStyleHooks,
|
|
2136
|
+
fixedEditorController: FixedEditorController,
|
|
2056
2137
|
toolGrouping?: ToolGroupingHooks,
|
|
2138
|
+
compactThinking?: CompactThinkingController,
|
|
2057
2139
|
): Promise<void> {
|
|
2058
2140
|
if (ctx?.mode !== "tui" || !ctx?.hasUI || typeof ctx.ui?.custom !== "function") {
|
|
2059
2141
|
ctx.ui?.notify?.("/ccstyle requires TUI mode", "warning");
|
|
@@ -2148,6 +2230,27 @@ async function showCcstylePanel(
|
|
|
2148
2230
|
),
|
|
2149
2231
|
values: [...EXPANDED_PREVIEW_MAX_LINES_VALUES],
|
|
2150
2232
|
};
|
|
2233
|
+
const thinkingTitleSetting = {
|
|
2234
|
+
id: "useSummaryTitlesAsThinkingTitle",
|
|
2235
|
+
label: "Summary title",
|
|
2236
|
+
description: "Use the latest provider summary as the active thinking title.",
|
|
2237
|
+
currentValue: config.useSummaryTitlesAsThinkingTitle ? "on" : "off",
|
|
2238
|
+
values: ["on", "off"],
|
|
2239
|
+
};
|
|
2240
|
+
const thinkingPreviewSetting = {
|
|
2241
|
+
id: "previewLines",
|
|
2242
|
+
label: "Preview lines",
|
|
2243
|
+
description: "Thinking preview lines; 0 hides the preview body.",
|
|
2244
|
+
currentValue: nearestPreset(config.previewLines, THINKING_PREVIEW_LINES_VALUES),
|
|
2245
|
+
values: [...THINKING_PREVIEW_LINES_VALUES],
|
|
2246
|
+
};
|
|
2247
|
+
const thinkingAnimationSetting = {
|
|
2248
|
+
id: "animationIntervalMs",
|
|
2249
|
+
label: "Animation interval ms",
|
|
2250
|
+
description: "Thinking title animation interval for the next thinking run.",
|
|
2251
|
+
currentValue: nearestPreset(config.animationIntervalMs, THINKING_ANIMATION_INTERVAL_VALUES),
|
|
2252
|
+
values: [...THINKING_ANIMATION_INTERVAL_VALUES],
|
|
2253
|
+
};
|
|
2151
2254
|
|
|
2152
2255
|
const onSettingChange = (id: string, value: string) => {
|
|
2153
2256
|
switch (id) {
|
|
@@ -2161,6 +2264,7 @@ async function showCcstylePanel(
|
|
|
2161
2264
|
config.fixedEditorFeatures,
|
|
2162
2265
|
);
|
|
2163
2266
|
saveConfig();
|
|
2267
|
+
fixedEditorController.setEnabled(config.fixedEditorFeatures);
|
|
2164
2268
|
installToolMouseInteraction(ctx);
|
|
2165
2269
|
refreshCurrentTranscript(compactStyle, ctx);
|
|
2166
2270
|
ctx.ui.notify(`Fixed editor: ${value}`, "info");
|
|
@@ -2207,10 +2311,23 @@ async function showCcstylePanel(
|
|
|
2207
2311
|
50_000,
|
|
2208
2312
|
);
|
|
2209
2313
|
break;
|
|
2314
|
+
case "useSummaryTitlesAsThinkingTitle":
|
|
2315
|
+
config.useSummaryTitlesAsThinkingTitle = value === "on";
|
|
2316
|
+
break;
|
|
2317
|
+
case "previewLines":
|
|
2318
|
+
config.previewLines = pickPositiveInt(value, DEFAULT_CONFIG.previewLines, 0);
|
|
2319
|
+
break;
|
|
2320
|
+
case "animationIntervalMs":
|
|
2321
|
+
config.animationIntervalMs = pickPositiveNumber(
|
|
2322
|
+
value,
|
|
2323
|
+
DEFAULT_CONFIG.animationIntervalMs,
|
|
2324
|
+
);
|
|
2325
|
+
break;
|
|
2210
2326
|
default:
|
|
2211
2327
|
return;
|
|
2212
2328
|
}
|
|
2213
2329
|
saveConfig();
|
|
2330
|
+
compactThinking?.updateConfig(getCompactThinkingConfig());
|
|
2214
2331
|
refreshCurrentTranscript(compactStyle, ctx);
|
|
2215
2332
|
ctx.ui.notify(`Updated ${id}: ${value}`, "info");
|
|
2216
2333
|
};
|
|
@@ -2238,6 +2355,11 @@ async function showCcstylePanel(
|
|
|
2238
2355
|
expandedMaxSetting,
|
|
2239
2356
|
],
|
|
2240
2357
|
},
|
|
2358
|
+
{
|
|
2359
|
+
id: "thinking",
|
|
2360
|
+
label: "Thinking",
|
|
2361
|
+
items: [thinkingTitleSetting, thinkingPreviewSetting, thinkingAnimationSetting],
|
|
2362
|
+
},
|
|
2241
2363
|
];
|
|
2242
2364
|
|
|
2243
2365
|
let activeSection = 0;
|
|
@@ -2999,17 +3121,35 @@ function notePendingScrollMessage(role: unknown): void {
|
|
|
2999
3121
|
toolMouseTui.requestRender?.();
|
|
3000
3122
|
}
|
|
3001
3123
|
|
|
3002
|
-
export default function (
|
|
3124
|
+
export default function (
|
|
3125
|
+
pi: ExtensionAPI,
|
|
3126
|
+
configOverride?: Partial<Config>,
|
|
3127
|
+
compactThinking?: CompactThinkingController,
|
|
3128
|
+
) {
|
|
3003
3129
|
// The optional override keeps integration tests independent from the user's global config.
|
|
3004
3130
|
if (configOverride) config = normalizeConfig({ ...config, ...configOverride });
|
|
3131
|
+
const fixedEditorController = installFixedEditor(pi, config.fixedEditorFeatures);
|
|
3005
3132
|
const writeExecutionMetadata = installWriteOverride(pi, new WriteExecutionMetadataStore());
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3133
|
+
let installation:
|
|
3134
|
+
| {
|
|
3135
|
+
globalToolRendering: GlobalToolRenderPatch;
|
|
3136
|
+
toolGrouping: ToolGroupingHooks;
|
|
3137
|
+
compactStyle: CompactStyleHooks;
|
|
3138
|
+
}
|
|
3139
|
+
| undefined;
|
|
3140
|
+
const ensureTuiInstallation = (ctx: any) => {
|
|
3141
|
+
if (ctx?.mode !== "tui" || !ctx?.hasUI) return undefined;
|
|
3142
|
+
if (installation) return installation;
|
|
3143
|
+
const globalToolRendering = installGlobalToolRendering(writeExecutionMetadata);
|
|
3144
|
+
const toolGrouping = installToolGrouping(() => config.mode === "on");
|
|
3145
|
+
deactivateLegacyCompactionRendering();
|
|
3146
|
+
const compactStyle = installCompactStyle(pi, {
|
|
3147
|
+
getMode: () => config.mode,
|
|
3148
|
+
getExcludeRenderers: () => config.excludeRenderers,
|
|
3149
|
+
});
|
|
3150
|
+
installation = { globalToolRendering, toolGrouping, compactStyle };
|
|
3151
|
+
return installation;
|
|
3152
|
+
};
|
|
3013
3153
|
|
|
3014
3154
|
pi.registerCommand("ccstyle", {
|
|
3015
3155
|
description: "Configure Claude Code style, fixed editor, and rich diff options",
|
|
@@ -3026,11 +3166,22 @@ export default function (pi: ExtensionAPI, configOverride?: Partial<Config>) {
|
|
|
3026
3166
|
handler: async (args, ctx) => {
|
|
3027
3167
|
const arg = args.trim().toLowerCase();
|
|
3028
3168
|
if (!arg || arg === "panel") {
|
|
3029
|
-
|
|
3169
|
+
const hooks = ensureTuiInstallation(ctx);
|
|
3170
|
+
if (hooks)
|
|
3171
|
+
await showCcstylePanel(
|
|
3172
|
+
ctx,
|
|
3173
|
+
hooks.compactStyle,
|
|
3174
|
+
fixedEditorController,
|
|
3175
|
+
hooks.toolGrouping,
|
|
3176
|
+
compactThinking,
|
|
3177
|
+
);
|
|
3178
|
+
else ctx.ui?.notify?.("/ccstyle requires TUI mode", "warning");
|
|
3030
3179
|
return;
|
|
3031
3180
|
}
|
|
3032
3181
|
if (arg === "on" || arg === "off" || arg === "compact") {
|
|
3033
|
-
|
|
3182
|
+
const hooks = ensureTuiInstallation(ctx);
|
|
3183
|
+
if (hooks) applyStyleMode(arg, ctx, hooks.compactStyle, hooks.toolGrouping);
|
|
3184
|
+
else ctx.ui?.notify?.("/ccstyle requires TUI mode", "warning");
|
|
3034
3185
|
return;
|
|
3035
3186
|
}
|
|
3036
3187
|
if (arg === "status") {
|
|
@@ -3042,69 +3193,81 @@ export default function (pi: ExtensionAPI, configOverride?: Partial<Config>) {
|
|
|
3042
3193
|
});
|
|
3043
3194
|
|
|
3044
3195
|
pi.on("session_start", async (event, ctx) => {
|
|
3045
|
-
|
|
3046
|
-
|
|
3196
|
+
const hooks = ensureTuiInstallation(ctx);
|
|
3197
|
+
if (!hooks) return;
|
|
3198
|
+
hooks.toolGrouping.setTheme(ctx.ui.theme);
|
|
3199
|
+
hooks.compactStyle.onSessionStart(event, ctx);
|
|
3047
3200
|
pendingScrollMessages = 0;
|
|
3048
3201
|
assistantMessageActive = false;
|
|
3049
3202
|
ctx.ui.setStatus("ccstyle", undefined);
|
|
3050
3203
|
installToolMouseInteraction(ctx);
|
|
3051
|
-
scheduleSessionRender(compactStyle.refresh);
|
|
3204
|
+
scheduleSessionRender(hooks.compactStyle.refresh);
|
|
3052
3205
|
});
|
|
3053
3206
|
|
|
3054
3207
|
pi.on("session_compact", async (event, ctx) => {
|
|
3055
|
-
|
|
3056
|
-
|
|
3208
|
+
const hooks = ensureTuiInstallation(ctx);
|
|
3209
|
+
if (!hooks) return;
|
|
3210
|
+
hooks.toolGrouping.setTheme(ctx.ui.theme);
|
|
3211
|
+
hooks.compactStyle.onSessionCompact(event, ctx);
|
|
3057
3212
|
// Compaction rebuilds the transcript without session_start. Rebind after
|
|
3058
3213
|
// other TUI extensions may have replaced the root input dispatcher.
|
|
3059
3214
|
installToolMouseInteraction(ctx);
|
|
3060
|
-
scheduleSessionRender(compactStyle.refresh);
|
|
3215
|
+
scheduleSessionRender(hooks.compactStyle.refresh);
|
|
3061
3216
|
});
|
|
3062
3217
|
|
|
3063
3218
|
pi.on("message_start", async (event) => {
|
|
3064
|
-
notePendingScrollMessage(event?.message?.role);
|
|
3219
|
+
if (installation) notePendingScrollMessage(event?.message?.role);
|
|
3065
3220
|
});
|
|
3066
3221
|
|
|
3067
3222
|
pi.on("message_update", async (event, ctx) => {
|
|
3068
|
-
compactStyle
|
|
3069
|
-
if (event?.message?.role === "assistant") notePendingScrollMessage("assistant");
|
|
3223
|
+
installation?.compactStyle.onMessageUpdate(event, ctx);
|
|
3224
|
+
if (installation && event?.message?.role === "assistant") notePendingScrollMessage("assistant");
|
|
3070
3225
|
});
|
|
3071
3226
|
|
|
3072
3227
|
pi.on("message_end", async (event) => {
|
|
3073
|
-
if (event?.message?.role === "assistant") assistantMessageActive = false;
|
|
3228
|
+
if (installation && event?.message?.role === "assistant") assistantMessageActive = false;
|
|
3074
3229
|
});
|
|
3075
3230
|
|
|
3076
3231
|
pi.on("agent_start", async (event, ctx) => {
|
|
3077
|
-
compactStyle
|
|
3232
|
+
installation?.compactStyle.onAgentStart(event, ctx);
|
|
3078
3233
|
});
|
|
3079
3234
|
|
|
3080
3235
|
pi.on("agent_end", async (event, ctx) => {
|
|
3081
|
-
compactStyle
|
|
3236
|
+
installation?.compactStyle.onAgentEnd(event, ctx);
|
|
3082
3237
|
});
|
|
3083
3238
|
|
|
3084
3239
|
pi.on("turn_start", async (event, ctx) => {
|
|
3085
|
-
compactStyle
|
|
3240
|
+
installation?.compactStyle.onTurnStart(event, ctx);
|
|
3086
3241
|
});
|
|
3087
3242
|
|
|
3088
3243
|
pi.on("tool_execution_start", async (event, ctx) => {
|
|
3089
|
-
toolGrouping.setTheme(ctx.ui.theme);
|
|
3090
|
-
compactStyle
|
|
3244
|
+
installation?.toolGrouping.setTheme(ctx.ui.theme);
|
|
3245
|
+
installation?.compactStyle.onToolExecutionStart(event, ctx);
|
|
3091
3246
|
});
|
|
3092
3247
|
|
|
3093
3248
|
pi.on("tool_execution_update", async (event, ctx) => {
|
|
3094
|
-
compactStyle
|
|
3249
|
+
installation?.compactStyle.onToolExecutionUpdate(event, ctx);
|
|
3095
3250
|
});
|
|
3096
3251
|
|
|
3097
3252
|
pi.on("tool_execution_end", async (event, ctx) => {
|
|
3098
|
-
compactStyle
|
|
3253
|
+
installation?.compactStyle.onToolExecutionEnd(event, ctx);
|
|
3099
3254
|
});
|
|
3100
3255
|
|
|
3101
3256
|
pi.on("session_shutdown", async (event, ctx) => {
|
|
3102
|
-
compactStyle?.onSessionShutdown(event, ctx);
|
|
3103
3257
|
writeExecutionMetadata.clear();
|
|
3258
|
+
const current = installation;
|
|
3259
|
+
if (
|
|
3260
|
+
!current ||
|
|
3261
|
+
(globalThis as any)[GLOBAL_TOOL_RENDER_PATCH] !== current.globalToolRendering ||
|
|
3262
|
+
!current.globalToolRendering.active
|
|
3263
|
+
)
|
|
3264
|
+
return;
|
|
3265
|
+
current.compactStyle.onSessionShutdown(event, ctx);
|
|
3104
3266
|
teardownToolMouseInteraction();
|
|
3105
|
-
deactivateGlobalToolRendering(globalToolRendering);
|
|
3106
|
-
toolGrouping.shutdown();
|
|
3267
|
+
deactivateGlobalToolRendering(current.globalToolRendering);
|
|
3268
|
+
current.toolGrouping.shutdown();
|
|
3107
3269
|
deactivateLegacyCompactionRendering();
|
|
3108
3270
|
clearAllAnimations();
|
|
3271
|
+
installation = undefined;
|
|
3109
3272
|
});
|
|
3110
3273
|
}
|