pi-cc-extensions 0.8.45 → 0.8.47
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 +11 -26
- package/README.md +40 -23
- package/assets/readme/welcome.webp +0 -0
- package/extensions/claude-code-style.ts +554 -519
- package/extensions/context.ts +196 -106
- package/extensions/index.ts +2 -2
- package/extensions/markdown-enhance.ts +208 -0
- package/extensions/pi-aliases.ts +0 -3
- package/extensions/tool-diff/diff-renderer.ts +9 -4
- package/extensions/tool-grouping.ts +2 -1
- package/package.json +10 -14
- package/themes/cc-dark.json +1 -7
- package/themes/cc-light.json +91 -0
- package/extensions/compact-style.ts +0 -1268
- package/extensions/fixed-editor.ts +0 -458
- package/extensions/subagent-notification.ts +0 -121
- package/node_modules/@tifan/pi-fixed-editor/README.md +0 -36
- package/node_modules/@tifan/pi-fixed-editor/package.json +0 -46
- package/node_modules/@tifan/pi-fixed-editor/src/cluster.ts +0 -113
- package/node_modules/@tifan/pi-fixed-editor/src/index.ts +0 -259
- package/node_modules/@tifan/pi-fixed-editor/src/terminal-split.ts +0 -1337
- package/themes/github-dark-default.json +0 -91
|
@@ -1,20 +1,8 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { getSettingsListTheme, ToolExecutionComponent } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { isFullscreenUi, isLazyProxyTui } from "./fullscreen-detect.ts";
|
|
4
|
-
import {
|
|
5
|
-
installCompactStyle,
|
|
6
|
-
type CompactStyleHooks,
|
|
7
|
-
type CompactStyleMode,
|
|
8
|
-
} from "./compact-style.ts";
|
|
9
4
|
import type { CompactThinkingConfig, CompactThinkingController } from "./compact-thinking.ts";
|
|
10
|
-
import { showTextPreview } from "./context.ts";
|
|
11
|
-
import {
|
|
12
|
-
getFixedEditorScrollButtonHitbox,
|
|
13
|
-
getFixedEditorViewport,
|
|
14
|
-
installFixedEditor,
|
|
15
|
-
setBeforeFixedEditorStart,
|
|
16
|
-
type FixedEditorController,
|
|
17
|
-
} from "./fixed-editor.ts";
|
|
5
|
+
import { hasActiveTextPreview, showTextPreview } from "./context.ts";
|
|
18
6
|
import {
|
|
19
7
|
installToolGrouping,
|
|
20
8
|
ToolGroupComponent,
|
|
@@ -34,6 +22,7 @@ import {
|
|
|
34
22
|
import {
|
|
35
23
|
SettingsList,
|
|
36
24
|
Text,
|
|
25
|
+
getKeybindings,
|
|
37
26
|
matchesKey,
|
|
38
27
|
truncateToWidth,
|
|
39
28
|
visibleWidth,
|
|
@@ -47,14 +36,15 @@ import { inspect } from "node:util";
|
|
|
47
36
|
/**
|
|
48
37
|
* Claude Code Style for pi.
|
|
49
38
|
*
|
|
50
|
-
* This is the package's only entry point.
|
|
51
|
-
*
|
|
39
|
+
* This is the package's only entry point. Tool rendering (summaries, rich
|
|
40
|
+
* edit/write diffs, expand/collapse) is applied via prototype-level patches.
|
|
52
41
|
*/
|
|
53
42
|
|
|
43
|
+
export type CompactStyleMode = "on" | "off";
|
|
44
|
+
|
|
54
45
|
export type Config = {
|
|
55
46
|
mode: CompactStyleMode;
|
|
56
47
|
excludeRenderers: string[];
|
|
57
|
-
fixedEditorFeatures: boolean;
|
|
58
48
|
diffViewMode: DiffViewMode;
|
|
59
49
|
diffIndicatorMode: DiffIndicatorMode;
|
|
60
50
|
diffSplitMinWidth: number;
|
|
@@ -93,7 +83,6 @@ const EXCLUDE_RENDERER_CANDIDATES = [
|
|
|
93
83
|
export const DEFAULT_CONFIG: Config = {
|
|
94
84
|
mode: "on",
|
|
95
85
|
excludeRenderers: [],
|
|
96
|
-
fixedEditorFeatures: true,
|
|
97
86
|
diffViewMode: DEFAULT_TOOL_DISPLAY_CONFIG.diffViewMode,
|
|
98
87
|
diffIndicatorMode: DEFAULT_TOOL_DISPLAY_CONFIG.diffIndicatorMode,
|
|
99
88
|
diffSplitMinWidth: DEFAULT_TOOL_DISPLAY_CONFIG.diffSplitMinWidth,
|
|
@@ -141,8 +130,9 @@ function nearestPreset(value: number, presets: readonly string[]): string {
|
|
|
141
130
|
export function normalizeConfig(input: unknown): Config {
|
|
142
131
|
const source = (input && typeof input === "object" ? input : {}) as Record<string, unknown>;
|
|
143
132
|
const mode = source.mode;
|
|
133
|
+
// mode=compact 已日落:旧配置回退到 on(Claude Code 风格)。
|
|
144
134
|
const migratedMode: CompactStyleMode =
|
|
145
|
-
mode === "on" || mode === "off"
|
|
135
|
+
mode === "on" || mode === "off"
|
|
146
136
|
? mode
|
|
147
137
|
: typeof source.enabled === "boolean"
|
|
148
138
|
? source.enabled
|
|
@@ -158,11 +148,9 @@ export function normalizeConfig(input: unknown): Config {
|
|
|
158
148
|
),
|
|
159
149
|
]
|
|
160
150
|
: [];
|
|
161
|
-
const fixedEditorFeatures = source.fixedEditorFeatures !== false;
|
|
162
151
|
return {
|
|
163
152
|
mode: migratedMode,
|
|
164
153
|
excludeRenderers,
|
|
165
|
-
fixedEditorFeatures,
|
|
166
154
|
diffViewMode: pickEnum(source.diffViewMode, DIFF_VIEW_MODES, DEFAULT_CONFIG.diffViewMode),
|
|
167
155
|
diffIndicatorMode: pickEnum(
|
|
168
156
|
source.diffIndicatorMode,
|
|
@@ -249,12 +237,7 @@ function loadConfig(): Config {
|
|
|
249
237
|
? (JSON.parse(readFileSync(CONFIG_PATH, "utf8")) as Record<string, unknown>)
|
|
250
238
|
: {};
|
|
251
239
|
const normalized = normalizeConfig(source);
|
|
252
|
-
if (
|
|
253
|
-
typeof source.enabled === "boolean" &&
|
|
254
|
-
source.mode !== "on" &&
|
|
255
|
-
source.mode !== "off" &&
|
|
256
|
-
source.mode !== "compact"
|
|
257
|
-
) {
|
|
240
|
+
if (typeof source.enabled === "boolean" && source.mode !== "on" && source.mode !== "off") {
|
|
258
241
|
try {
|
|
259
242
|
writeFileSync(CONFIG_PATH, JSON.stringify(normalized, null, 2));
|
|
260
243
|
} catch {
|
|
@@ -987,18 +970,9 @@ const TOOL_MOUSE_DISABLE = "\x1b[?1006l\x1b[?1003l\x1b[?1000l";
|
|
|
987
970
|
const ZENTUI_PAGE_UP_INPUT = /^\x1b\[5;9(?::[12])?~$|^\x1b\[57421;9(?::[12])?u$|^\x1b\[1;6A$/;
|
|
988
971
|
const ZENTUI_PAGE_DOWN_INPUT = /^\x1b\[6;9(?::[12])?~$|^\x1b\[57422;9(?::[12])?u$|^\x1b\[1;6B$/;
|
|
989
972
|
const SCROLL_BOTTOM_SHORTCUT = "ctrl+end";
|
|
990
|
-
const ZENTUI_WHEEL_ROWS = 3;
|
|
991
|
-
const FIXED_EDITOR_WHEEL_ROWS = 5;
|
|
992
973
|
let toolMouseTui: any = null;
|
|
993
974
|
let toolMouseUi: any = null;
|
|
994
|
-
let toolMouseFixedFeaturesEnabled = false;
|
|
995
|
-
let wheelExtraRowRemainder = 0;
|
|
996
|
-
let lastWheelDirection: "up" | "down" | null = null;
|
|
997
|
-
let collapseCompensationRemainder = 0;
|
|
998
975
|
let toolMouseInputUnsubscribe: (() => void) | null = null;
|
|
999
|
-
let toolMouseInputPatchTui: any = null;
|
|
1000
|
-
let toolMouseInputPatchOriginalHandle: ((...args: any[]) => any) | null = null;
|
|
1001
|
-
let toolMouseInputPatchWrapper: ((...args: any[]) => any) | null = null;
|
|
1002
976
|
let toolMouseRenderPatchTui: any = null;
|
|
1003
977
|
let toolMouseRenderPatchOriginal: ((...args: any[]) => any) | null = null;
|
|
1004
978
|
let toolMouseRenderPatchWrapper: ((...args: any[]) => any) | null = null;
|
|
@@ -1007,8 +981,6 @@ let toolMouseRawWrite: ((data: string) => unknown) | null = null;
|
|
|
1007
981
|
let scrollButtonVisible = false;
|
|
1008
982
|
let scrollButtonHovered = false;
|
|
1009
983
|
let scrollButtonWidget: any = null;
|
|
1010
|
-
let pendingScrollMessages = 0;
|
|
1011
|
-
let assistantMessageActive = false;
|
|
1012
984
|
let scrollButtonSyncScheduled = false;
|
|
1013
985
|
let sessionRenderTimer: ReturnType<typeof setTimeout> | null = null;
|
|
1014
986
|
let hoveredToolCallId: string | null = null;
|
|
@@ -1113,24 +1085,10 @@ function extractToolFramePlacements(
|
|
|
1113
1085
|
/** Summary markers used by Pi and ccstyle; unlike the trailing hint, these survive truncation. */
|
|
1114
1086
|
const COLLAPSED_TOOL_SUMMARY = /^\s*(?:↳|└|⎿|●|✓|✗|…)/;
|
|
1115
1087
|
|
|
1116
|
-
function isFixedEditorTui(tui: any): boolean {
|
|
1117
|
-
const terminal = tui?.terminal;
|
|
1118
|
-
if (!terminal) return false;
|
|
1119
|
-
const ownRows = Object.getOwnPropertyDescriptor(terminal, "rows");
|
|
1120
|
-
const prototype = Object.getPrototypeOf(terminal);
|
|
1121
|
-
const inheritedRows = prototype ? Object.getOwnPropertyDescriptor(prototype, "rows") : undefined;
|
|
1122
|
-
return typeof ownRows?.get === "function" && ownRows.get !== inheritedRows?.get;
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
function useFixedEditorFeatures(tui: any): boolean {
|
|
1126
|
-
return toolMouseFixedFeaturesEnabled && isFixedEditorTui(tui);
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
/** Mouse hover/click affordances need ccstyle rendering AND the fixed editor. Without
|
|
1130
|
-
* the fixed editor, Pi's native TUI has no mouse handling, so any reporting mode leaves
|
|
1131
|
-
* the wheel in a dead zone — native mode must leave reporting off entirely. */
|
|
1132
1088
|
function toolMouseInteractionActive(): boolean {
|
|
1133
|
-
|
|
1089
|
+
if (config.mode === "off") return false;
|
|
1090
|
+
if (isLazyProxyTui(toolMouseTui)) return true;
|
|
1091
|
+
return true;
|
|
1134
1092
|
}
|
|
1135
1093
|
|
|
1136
1094
|
function formatShortcut(shortcut: string): string {
|
|
@@ -1157,24 +1115,23 @@ function wheelDirection(data: string): "up" | "down" | null {
|
|
|
1157
1115
|
return null;
|
|
1158
1116
|
}
|
|
1159
1117
|
|
|
1160
|
-
/** Return how often Zentui's 3-row wheel handler should receive this event. */
|
|
1161
|
-
export function fixedEditorWheelDispatchCount(direction: "up" | "down"): number {
|
|
1162
|
-
if (lastWheelDirection !== direction) {
|
|
1163
|
-
lastWheelDirection = direction;
|
|
1164
|
-
wheelExtraRowRemainder = 0;
|
|
1165
|
-
}
|
|
1166
|
-
wheelExtraRowRemainder += FIXED_EDITOR_WHEEL_ROWS - ZENTUI_WHEEL_ROWS;
|
|
1167
|
-
if (wheelExtraRowRemainder < ZENTUI_WHEEL_ROWS) return 1;
|
|
1168
|
-
wheelExtraRowRemainder -= ZENTUI_WHEEL_ROWS;
|
|
1169
|
-
return 2;
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
1118
|
function isScrollNavigationInput(data: string): boolean {
|
|
1173
1119
|
if (
|
|
1174
1120
|
matchesKey(data, "pageUp") ||
|
|
1175
1121
|
matchesKey(data, "pageDown") ||
|
|
1176
1122
|
ZENTUI_PAGE_UP_INPUT.test(data) ||
|
|
1177
|
-
ZENTUI_PAGE_DOWN_INPUT.test(data)
|
|
1123
|
+
ZENTUI_PAGE_DOWN_INPUT.test(data) ||
|
|
1124
|
+
// 官方 fullscreen viewport 的可滚动键(half-page/prompt/top/bottom)。
|
|
1125
|
+
[
|
|
1126
|
+
"tui.altScreen.pageUp",
|
|
1127
|
+
"tui.altScreen.pageDown",
|
|
1128
|
+
"tui.altScreen.halfPageUp",
|
|
1129
|
+
"tui.altScreen.halfPageDown",
|
|
1130
|
+
"tui.altScreen.previousPrompt",
|
|
1131
|
+
"tui.altScreen.nextPrompt",
|
|
1132
|
+
"tui.altScreen.top",
|
|
1133
|
+
"tui.altScreen.bottom",
|
|
1134
|
+
].some((key) => getKeybindings().matches(data, key))
|
|
1178
1135
|
) {
|
|
1179
1136
|
return true;
|
|
1180
1137
|
}
|
|
@@ -1187,77 +1144,22 @@ function isScrollNavigationInput(data: string): boolean {
|
|
|
1187
1144
|
);
|
|
1188
1145
|
}
|
|
1189
1146
|
|
|
1190
|
-
function
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
} catch {
|
|
1195
|
-
return [];
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
/** Index just before the fixed editor cluster in the TUI child list. */
|
|
1200
|
-
function fixedScrollableRootEnd(tui: any): number {
|
|
1201
|
-
const children = Array.isArray(tui?.children) ? tui.children : [];
|
|
1202
|
-
const editorIndex = children.findIndex((child: any) =>
|
|
1203
|
-
containsEditorLike(child, tui.focusedComponent),
|
|
1204
|
-
);
|
|
1205
|
-
return editorIndex >= 2 ? editorIndex - 2 : children.length;
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
/**
|
|
1209
|
-
* Last N stripped lines of the scrollable root (after trimming trailing blanks).
|
|
1210
|
-
* Walks children backwards and stops once the tail is fully determined, so long
|
|
1211
|
-
* transcripts with many sibling nodes do not re-render the whole tree on scroll.
|
|
1212
|
-
*/
|
|
1213
|
-
function renderFixedScrollableRootTail(tui: any, width: number, matchLength: number): string[] {
|
|
1214
|
-
const children = Array.isArray(tui?.children) ? tui.children : [];
|
|
1215
|
-
const end = fixedScrollableRootEnd(tui);
|
|
1216
|
-
const collected: string[] = [];
|
|
1217
|
-
for (let index = end - 1; index >= 0; index--) {
|
|
1218
|
-
const lines = directRenderLines(children[index], width).map((line) =>
|
|
1219
|
-
stripTerminalSequences(String(line)),
|
|
1220
|
-
);
|
|
1221
|
-
collected.unshift(...lines);
|
|
1222
|
-
let meaningful = collected.length;
|
|
1223
|
-
while (meaningful > 0 && collected[meaningful - 1] === "") meaningful--;
|
|
1224
|
-
if (meaningful >= matchLength) break;
|
|
1225
|
-
}
|
|
1226
|
-
while (collected.length > 0 && collected[collected.length - 1] === "") collected.pop();
|
|
1227
|
-
if (collected.length === 0) return [];
|
|
1228
|
-
return collected.slice(-Math.min(matchLength, collected.length));
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
function isFixedEditorAtBottom(tui: any): boolean {
|
|
1232
|
-
if (!useFixedEditorFeatures(tui)) return true;
|
|
1233
|
-
const visibleLines = Array.isArray(tui?.previousLines) ? tui.previousLines : [];
|
|
1234
|
-
if (visibleLines.length === 0) return true;
|
|
1235
|
-
const width = Math.max(1, Number(tui?.terminal?.columns) || 80);
|
|
1236
|
-
const expected = renderFixedScrollableRootTail(tui, width, 3);
|
|
1237
|
-
if (expected.length === 0) return true;
|
|
1238
|
-
|
|
1239
|
-
// previousLines contains both the scrollable root and Zentui's fixed cluster.
|
|
1240
|
-
// Locate the root tail within that full frame instead of requiring it to be
|
|
1241
|
-
// the frame suffix; otherwise status/editor/footer rows keep the button alive.
|
|
1242
|
-
const visible = visibleLines.map((line: unknown) => stripTerminalSequences(String(line)));
|
|
1243
|
-
const matchLength = expected.length;
|
|
1244
|
-
for (let end = matchLength; end <= visible.length; end++) {
|
|
1245
|
-
if (expected.every((line, index) => line === visible[end - matchLength + index])) return true;
|
|
1246
|
-
}
|
|
1247
|
-
return false;
|
|
1147
|
+
function isAtTranscriptBottom(tui: any): boolean {
|
|
1148
|
+
// 惰性 Proxy fullscreen:官方 viewport 以 isFollowingOutput 判定是否在底部。
|
|
1149
|
+
if (fullscreenLazyTui(tui)) return isFullscreenAtBottom(tui);
|
|
1150
|
+
return true;
|
|
1248
1151
|
}
|
|
1249
1152
|
|
|
1250
1153
|
function hideScrollButton(tui: any): void {
|
|
1251
|
-
const changed = scrollButtonVisible || scrollButtonHovered
|
|
1154
|
+
const changed = scrollButtonVisible || scrollButtonHovered;
|
|
1252
1155
|
scrollButtonVisible = false;
|
|
1253
1156
|
scrollButtonHovered = false;
|
|
1254
|
-
pendingScrollMessages = 0;
|
|
1255
1157
|
if (changed) tui.requestRender?.();
|
|
1256
1158
|
}
|
|
1257
1159
|
|
|
1258
1160
|
function scheduleScrollButtonSync(tui: any, data: string): void {
|
|
1259
1161
|
if (
|
|
1260
|
-
!
|
|
1162
|
+
!fullscreenLazyTui(tui) ||
|
|
1261
1163
|
!toolMouseInteractionActive() ||
|
|
1262
1164
|
!isScrollNavigationInput(data) ||
|
|
1263
1165
|
scrollButtonSyncScheduled
|
|
@@ -1271,7 +1173,8 @@ function scheduleScrollButtonSync(tui: any, data: string): void {
|
|
|
1271
1173
|
// Pi renders on its own frame timer. Inspect the resulting viewport before
|
|
1272
1174
|
// showing the button so empty or non-scrollable transcripts never flash it.
|
|
1273
1175
|
const rendered = tui.previousLines !== previousLines;
|
|
1274
|
-
|
|
1176
|
+
// fullscreen 下 isFollowingOutput 是即时状态,无需等待官方帧渲染。
|
|
1177
|
+
if (!rendered && attempt < 4 && !fullscreenLazyTui(tui)) {
|
|
1275
1178
|
scrollButtonSyncScheduled = true;
|
|
1276
1179
|
const timer = setTimeout(() => check(attempt + 1), 16);
|
|
1277
1180
|
if (typeof timer === "object" && timer !== null && "unref" in timer) {
|
|
@@ -1279,8 +1182,7 @@ function scheduleScrollButtonSync(tui: any, data: string): void {
|
|
|
1279
1182
|
}
|
|
1280
1183
|
return;
|
|
1281
1184
|
}
|
|
1282
|
-
const nextVisible = !
|
|
1283
|
-
if (!nextVisible) pendingScrollMessages = 0;
|
|
1185
|
+
const nextVisible = !isAtTranscriptBottom(tui);
|
|
1284
1186
|
if (nextVisible !== scrollButtonVisible) {
|
|
1285
1187
|
scrollButtonVisible = nextVisible;
|
|
1286
1188
|
tui.requestRender?.();
|
|
@@ -1290,7 +1192,7 @@ function scheduleScrollButtonSync(tui: any, data: string): void {
|
|
|
1290
1192
|
}
|
|
1291
1193
|
|
|
1292
1194
|
function updateScrollButtonFromInput(tui: any, data: string): void {
|
|
1293
|
-
if (!
|
|
1195
|
+
if (!fullscreenLazyTui(tui) || !toolMouseInteractionActive()) return;
|
|
1294
1196
|
if (matchesKey(data, "enter") || matchesKey(data, "return")) hideScrollButton(tui);
|
|
1295
1197
|
}
|
|
1296
1198
|
|
|
@@ -1306,70 +1208,6 @@ function renderComponentTree(component: any, width: number): string[] {
|
|
|
1306
1208
|
return component.children.flatMap((child: any) => renderComponentTree(child, width));
|
|
1307
1209
|
}
|
|
1308
1210
|
|
|
1309
|
-
function containsEditorLike(component: any, focused: any, seen = new Set<any>()): boolean {
|
|
1310
|
-
if (!component || typeof component !== "object" || seen.has(component)) return false;
|
|
1311
|
-
seen.add(component);
|
|
1312
|
-
if (component === focused) return true;
|
|
1313
|
-
if (
|
|
1314
|
-
typeof component.getText === "function" &&
|
|
1315
|
-
typeof component.setText === "function" &&
|
|
1316
|
-
typeof component.handleInput === "function"
|
|
1317
|
-
)
|
|
1318
|
-
return true;
|
|
1319
|
-
return (
|
|
1320
|
-
Array.isArray(component.children) &&
|
|
1321
|
-
component.children.some((child: any) => containsEditorLike(child, focused, seen))
|
|
1322
|
-
);
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
function isScrollButtonAtScreenRow(_tui: any, packet: SgrMousePacket): boolean {
|
|
1326
|
-
return interactionRegionAt(packet)?.kind === "scroll-bottom";
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
function jumpToBottomWithoutSubmit(tui: any): boolean {
|
|
1330
|
-
const originalHandle = toolMouseInputPatchTui === tui ? toolMouseInputPatchOriginalHandle : null;
|
|
1331
|
-
if (!originalHandle) return false;
|
|
1332
|
-
|
|
1333
|
-
// Route Enter through Pi's normal listener chain so pi-zentui can update its
|
|
1334
|
-
// private scroll offset, but suppress the focused editor for this synthetic
|
|
1335
|
-
// dispatch so clicking the button never submits the current input.
|
|
1336
|
-
const focused = tui.focusedComponent;
|
|
1337
|
-
try {
|
|
1338
|
-
tui.focusedComponent = null;
|
|
1339
|
-
Reflect.apply(originalHandle, tui, ["\r"]);
|
|
1340
|
-
} finally {
|
|
1341
|
-
tui.focusedComponent = focused;
|
|
1342
|
-
}
|
|
1343
|
-
hideScrollButton(tui);
|
|
1344
|
-
return true;
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
function handleScrollButtonClick(tui: any, packet: SgrMousePacket): boolean {
|
|
1348
|
-
if (!isScrollButtonAtScreenRow(tui, packet)) return false;
|
|
1349
|
-
return jumpToBottomWithoutSubmit(tui);
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
function scheduleCollapseViewportCompensation(
|
|
1353
|
-
tui: any,
|
|
1354
|
-
removedRows: number,
|
|
1355
|
-
packet: SgrMousePacket,
|
|
1356
|
-
): void {
|
|
1357
|
-
if (removedRows <= 0 || !useFixedEditorFeatures(tui)) return;
|
|
1358
|
-
const originalHandle = toolMouseInputPatchTui === tui ? toolMouseInputPatchOriginalHandle : null;
|
|
1359
|
-
if (!originalHandle) return;
|
|
1360
|
-
|
|
1361
|
-
process.nextTick(() => {
|
|
1362
|
-
if (toolMouseTui !== tui || toolMouseInputPatchOriginalHandle !== originalHandle) return;
|
|
1363
|
-
const targetRows = removedRows + collapseCompensationRemainder;
|
|
1364
|
-
const dispatches = Math.max(0, Math.round(targetRows / ZENTUI_WHEEL_ROWS));
|
|
1365
|
-
collapseCompensationRemainder = targetRows - dispatches * ZENTUI_WHEEL_ROWS;
|
|
1366
|
-
const wheelDown = `\x1b[<65;${packet.col};${packet.row}M`;
|
|
1367
|
-
for (let index = 0; index < dispatches; index++) {
|
|
1368
|
-
Reflect.apply(originalHandle, tui, [wheelDown]);
|
|
1369
|
-
}
|
|
1370
|
-
});
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
1211
|
const ioViewInvalidators = new WeakMap<ExpandedToolIoView, () => void>();
|
|
1374
1212
|
let hoveredToolIoView: ExpandedToolIoView | null = null;
|
|
1375
1213
|
let hoveredToolIoSection: ToolIoSection | null = null;
|
|
@@ -1419,16 +1257,19 @@ function tryOpenToolIoShowMore(region: InteractionRegion): boolean {
|
|
|
1419
1257
|
}
|
|
1420
1258
|
|
|
1421
1259
|
function setHoveredToolIo(view: ExpandedToolIoView | null, section: ToolIoSection | null): boolean {
|
|
1422
|
-
|
|
1423
|
-
|
|
1260
|
+
// resultRendererComponent 可能是 Text/第三方 renderer;reload 后也可能残留旧实例。
|
|
1261
|
+
const nextView = isExpandedToolIoView(view) ? view : null;
|
|
1262
|
+
const nextSection = nextView ? section : null;
|
|
1263
|
+
if (nextView === hoveredToolIoView && nextSection === hoveredToolIoSection) return false;
|
|
1264
|
+
if (isExpandedToolIoView(hoveredToolIoView)) {
|
|
1424
1265
|
hoveredToolIoView.setHoveredSection(null);
|
|
1425
1266
|
ioViewInvalidators.get(hoveredToolIoView)?.();
|
|
1426
1267
|
}
|
|
1427
|
-
hoveredToolIoView =
|
|
1428
|
-
hoveredToolIoSection =
|
|
1429
|
-
if (
|
|
1430
|
-
|
|
1431
|
-
ioViewInvalidators.get(
|
|
1268
|
+
hoveredToolIoView = nextView;
|
|
1269
|
+
hoveredToolIoSection = nextSection;
|
|
1270
|
+
if (nextView) {
|
|
1271
|
+
nextView.setHoveredSection(nextSection);
|
|
1272
|
+
ioViewInvalidators.get(nextView)?.();
|
|
1432
1273
|
}
|
|
1433
1274
|
return true;
|
|
1434
1275
|
}
|
|
@@ -1466,21 +1307,18 @@ function updateToolSummaryHover(tui: any, packet: SgrMousePacket): void {
|
|
|
1466
1307
|
function toggleToolAtMouseClick(tui: any, packet: SgrMousePacket): boolean {
|
|
1467
1308
|
const region = interactionRegionAt(packet);
|
|
1468
1309
|
if (!region) return false;
|
|
1469
|
-
if (region.kind === "scroll-bottom") return
|
|
1310
|
+
if (region.kind === "scroll-bottom") return false;
|
|
1470
1311
|
if (region.kind === "show-more") return tryOpenToolIoShowMore(region);
|
|
1471
1312
|
const component = region.component;
|
|
1472
1313
|
if (!component) return false;
|
|
1473
1314
|
const width = Math.max(1, Number(tui?.terminal?.columns) || 80);
|
|
1474
1315
|
if (region.kind === "expanded-card") {
|
|
1475
|
-
const previousHeight = renderComponentTree(component, width).length;
|
|
1476
1316
|
component.setExpanded(false);
|
|
1477
1317
|
hoveredToolCallId = null;
|
|
1478
1318
|
setHoveredToolGroup(null);
|
|
1479
1319
|
setHoveredToolIo(null, null);
|
|
1480
1320
|
component.invalidate?.();
|
|
1481
|
-
const nextHeight = renderComponentTree(component, width).length;
|
|
1482
1321
|
tui.requestRender?.();
|
|
1483
|
-
scheduleCollapseViewportCompensation(tui, previousHeight - nextHeight, packet);
|
|
1484
1322
|
return true;
|
|
1485
1323
|
}
|
|
1486
1324
|
component.setExpanded(true);
|
|
@@ -1493,90 +1331,392 @@ function toggleToolAtMouseClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
1493
1331
|
}
|
|
1494
1332
|
|
|
1495
1333
|
function renderScrollButton(width: number, theme: any): string[] {
|
|
1496
|
-
if (!scrollButtonVisible || !
|
|
1334
|
+
if (!scrollButtonVisible || !fullscreenLazyTui(toolMouseTui)) return [];
|
|
1497
1335
|
const shortcut = formatShortcut(SCROLL_BOTTOM_SHORTCUT);
|
|
1498
|
-
const messageText =
|
|
1499
|
-
pendingScrollMessages > 0
|
|
1500
|
-
? `${pendingScrollMessages} new message${pendingScrollMessages === 1 ? "" : "s"}`
|
|
1501
|
-
: "Back to bottom";
|
|
1502
1336
|
const label = theme.fg(
|
|
1503
1337
|
scrollButtonHovered ? "text" : "accent",
|
|
1504
|
-
`[ ↓
|
|
1338
|
+
`[ ↓ Back to bottom · ${shortcut} ]`,
|
|
1505
1339
|
);
|
|
1506
1340
|
const leftPad = Math.max(0, Math.floor((width - visibleWidth(label)) / 2));
|
|
1507
1341
|
return [`${" ".repeat(leftPad)}${truncateToWidth(label, width, "…")}`];
|
|
1508
1342
|
}
|
|
1509
1343
|
|
|
1344
|
+
/** 惰性 Proxy 官方 fullscreen(TuiAltScreen)判定。 */
|
|
1345
|
+
function fullscreenLazyTui(tui: any): boolean {
|
|
1346
|
+
return isLazyProxyTui(tui) && tui.mode === "fullscreen";
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
/** 官方 fullscreen:是否已跟随 transcript 底部(按钮隐藏条件)。 */
|
|
1350
|
+
function isFullscreenAtBottom(tui: any): boolean {
|
|
1351
|
+
const following = tui.isFollowingOutput ?? tui.getPrimaryScrollView?.()?.isFollowingEnd ?? true;
|
|
1352
|
+
return Boolean(following);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
const FULLSCREEN_VIEWPORT_PATCH = Symbol("ccstyle.fullscreen-viewport-patch");
|
|
1356
|
+
const FULLSCREEN_WHEEL_SCROLL_ORIGINAL = Symbol("ccstyle.fullscreen-wheel-scroll-original");
|
|
1357
|
+
const FULLSCREEN_WHEEL_SCROLL_LINES = 3;
|
|
1358
|
+
|
|
1359
|
+
/** 布局树点查询:返回 (x,y) 处最深含行 leaf box(屏幕行 → 组件局部行)。 */
|
|
1360
|
+
function fullscreenLeafAt(
|
|
1361
|
+
layout: any,
|
|
1362
|
+
x: number,
|
|
1363
|
+
y: number,
|
|
1364
|
+
): { box: any; localRow: number } | null {
|
|
1365
|
+
const root = layout?.root;
|
|
1366
|
+
if (!root) return null;
|
|
1367
|
+
let best: { box: any; localRow: number } | null = null;
|
|
1368
|
+
let bestDepth = -1;
|
|
1369
|
+
const visit = (box: any, depth: number) => {
|
|
1370
|
+
if (!box) return;
|
|
1371
|
+
const clip = box.clip;
|
|
1372
|
+
if (!clip || x < clip.x || x >= clip.x + clip.width || y < clip.y || y >= clip.y + clip.height)
|
|
1373
|
+
return;
|
|
1374
|
+
const isLeaf = !Array.isArray(box.children) || box.children.length === 0;
|
|
1375
|
+
if (
|
|
1376
|
+
isLeaf &&
|
|
1377
|
+
y >= box.rect.y &&
|
|
1378
|
+
y < box.rect.y + Math.max(1, box.rect.height) &&
|
|
1379
|
+
depth > bestDepth
|
|
1380
|
+
) {
|
|
1381
|
+
best = { box, localRow: Math.max(0, y - box.rect.y) };
|
|
1382
|
+
bestDepth = depth;
|
|
1383
|
+
}
|
|
1384
|
+
for (const child of box.children ?? []) visit(child, depth + 1);
|
|
1385
|
+
};
|
|
1386
|
+
visit(root, 0);
|
|
1387
|
+
return best;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
/** 点击列是否为官方滚动条列(放行官方拖动)。 */
|
|
1391
|
+
function isScrollbarColumnAt(layout: any, x: number): boolean {
|
|
1392
|
+
let hit = false;
|
|
1393
|
+
const visit = (box: any) => {
|
|
1394
|
+
if (hit || !box) return;
|
|
1395
|
+
if (box.scrollView?.isScrollbarVisible && x === box.rect.x + box.rect.width - 1) {
|
|
1396
|
+
hit = true;
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
for (const child of box.children ?? []) visit(child);
|
|
1400
|
+
};
|
|
1401
|
+
visit(layout?.root);
|
|
1402
|
+
return hit;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1510
1405
|
/**
|
|
1511
|
-
*
|
|
1512
|
-
*
|
|
1513
|
-
* Keyboard, wheel, drag, release, and non-tool clicks continue through Pi's
|
|
1514
|
-
* original dispatcher, preserving pi-zentui's scroll-to-bottom behavior.
|
|
1406
|
+
* 布局 leaf box 的组件通常是容器(documentContainer/dock 容器等),工具卡与
|
|
1407
|
+
* widget 在其 children 内。按局部行遍历组件树,定位实际命中的子组件。
|
|
1515
1408
|
*/
|
|
1516
|
-
function
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1409
|
+
function componentAtLocalRow(
|
|
1410
|
+
component: any,
|
|
1411
|
+
localRow: number,
|
|
1412
|
+
width: number,
|
|
1413
|
+
): { component: any; row: number } | null {
|
|
1414
|
+
if (
|
|
1415
|
+
isToolExecutionComponent(component) ||
|
|
1416
|
+
component instanceof ToolGroupComponent ||
|
|
1417
|
+
component === scrollButtonWidget
|
|
1418
|
+
) {
|
|
1419
|
+
return { component, row: localRow };
|
|
1420
|
+
}
|
|
1421
|
+
if (!Array.isArray(component.children)) return null;
|
|
1422
|
+
let offset = 0;
|
|
1423
|
+
for (const child of component.children) {
|
|
1424
|
+
let lines: string[] = [];
|
|
1425
|
+
try {
|
|
1426
|
+
const rendered = child.render?.(width);
|
|
1427
|
+
if (Array.isArray(rendered)) lines = rendered.map((line) => String(line));
|
|
1428
|
+
} catch {
|
|
1429
|
+
lines = [];
|
|
1430
|
+
}
|
|
1431
|
+
if (localRow < offset + lines.length) {
|
|
1432
|
+
return (
|
|
1433
|
+
componentAtLocalRow(child, localRow - offset, width) ?? {
|
|
1434
|
+
component: child,
|
|
1435
|
+
row: localRow - offset,
|
|
1436
|
+
}
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
offset += lines.length;
|
|
1440
|
+
}
|
|
1441
|
+
return null;
|
|
1442
|
+
}
|
|
1520
1443
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
if (typeof
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1444
|
+
/** fullscreen single-expand:group 作为整体,不继续递归其内部工具。 */
|
|
1445
|
+
function collectFullscreenToolCards(component: any, out: any[], seen = new Set<any>()): void {
|
|
1446
|
+
if (!component || typeof component !== "object" || seen.has(component)) return;
|
|
1447
|
+
seen.add(component);
|
|
1448
|
+
if (isToolExecutionComponent(component) || component instanceof ToolGroupComponent) {
|
|
1449
|
+
out.push(component);
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
if (!Array.isArray(component.children)) return;
|
|
1453
|
+
for (const child of component.children) collectFullscreenToolCards(child, out, seen);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
/**
|
|
1457
|
+
* 官方 fullscreen 工具卡点击:collapsed 整卡点击展开
|
|
1458
|
+
* (有且仅保持一个展开:展开前收起其他工具卡),expanded 整卡二次点击收起,
|
|
1459
|
+
* 截断头 [show more] 打开全量预览;回到底部按钮 scrollToBottom。
|
|
1460
|
+
* 滚动条列、含 OSC8 链接行、非工具区域放行官方。
|
|
1461
|
+
*/
|
|
1462
|
+
function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
|
|
1463
|
+
const layout = tui.currentLayout;
|
|
1464
|
+
if (!layout?.root) return false;
|
|
1465
|
+
// 官方事件坐标 0-based;SGR packet 1-based。
|
|
1466
|
+
const x = packet.col - 1;
|
|
1467
|
+
const y = packet.row - 1;
|
|
1468
|
+
if (isScrollbarColumnAt(layout, x)) return false;
|
|
1469
|
+
const hit = fullscreenLeafAt(layout, x, y);
|
|
1470
|
+
if (!hit) return false;
|
|
1471
|
+
const width = Math.max(1, Number(tui.terminal?.columns) || 80);
|
|
1472
|
+
// 布局树用 scroll 的 contentWidth 渲染内容(滚动条占用时 = width-1);
|
|
1473
|
+
// 行号定位必须用同一宽度,否则换行差异导致组件行错位。
|
|
1474
|
+
const contentWidth =
|
|
1475
|
+
typeof hit.box.scrollView?.getContentWidth === "function"
|
|
1476
|
+
? Math.max(1, hit.box.scrollView.getContentWidth(width))
|
|
1477
|
+
: width;
|
|
1478
|
+
const target = componentAtLocalRow(hit.box.component, hit.localRow, contentWidth);
|
|
1479
|
+
if (!target) return false;
|
|
1480
|
+
const component = target.component;
|
|
1481
|
+
// 回到底部按钮:按组件引用命中,不依赖渲染行缓存。
|
|
1482
|
+
if (scrollButtonVisible && component === scrollButtonWidget) {
|
|
1483
|
+
tui.scrollToBottom?.();
|
|
1484
|
+
hideScrollButton(tui);
|
|
1485
|
+
return true;
|
|
1486
|
+
}
|
|
1487
|
+
const line = hit.box.lines?.[hit.localRow];
|
|
1488
|
+
if (typeof line !== "string" || /\x1b]8;[^;]*;/.test(line)) return false;
|
|
1489
|
+
const isTool = isToolExecutionComponent(component);
|
|
1490
|
+
const isGroup = component instanceof ToolGroupComponent;
|
|
1491
|
+
if (!isTool && !isGroup) return false;
|
|
1492
|
+
if (!component.expanded) {
|
|
1493
|
+
// single-expand:展开前收起其他已展开工具卡/group。
|
|
1494
|
+
const others: any[] = [];
|
|
1495
|
+
collectFullscreenToolCards(hit.box.component, others);
|
|
1496
|
+
for (const other of others) {
|
|
1497
|
+
if (other !== component && other.expanded) {
|
|
1498
|
+
other.setExpanded(false);
|
|
1499
|
+
other.invalidate?.();
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
component.setExpanded(true);
|
|
1503
|
+
} else {
|
|
1504
|
+
// 普通工具截断头 [show more]:打开全量预览(不收起)。
|
|
1505
|
+
const view = isTool ? component.resultRendererComponent : null;
|
|
1506
|
+
if (isExpandedToolIoView(view)) {
|
|
1507
|
+
const plain = stripTerminalSequencesPreservingLayout(line);
|
|
1508
|
+
const section = view.matchShowMoreLine(plain);
|
|
1509
|
+
if (section) {
|
|
1510
|
+
const box = view.showMoreHitbox(plain);
|
|
1511
|
+
if (box && x + 1 >= box.startCol && x + 1 <= box.endCol) {
|
|
1512
|
+
return tryOpenToolIoShowMore({
|
|
1513
|
+
kind: "show-more",
|
|
1514
|
+
row: 0,
|
|
1515
|
+
startCol: box.startCol,
|
|
1516
|
+
endCol: box.endCol,
|
|
1517
|
+
component,
|
|
1518
|
+
view,
|
|
1519
|
+
section,
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
// 整卡二次点击:收起(与展开语义对称)。
|
|
1525
|
+
component.setExpanded(false);
|
|
1526
|
+
}
|
|
1527
|
+
// 点击后清 hover 高亮。
|
|
1528
|
+
hoveredToolCallId = null;
|
|
1529
|
+
setHoveredToolGroup(null);
|
|
1530
|
+
setHoveredToolIo(null, null);
|
|
1531
|
+
component.invalidate?.();
|
|
1532
|
+
tui.requestRender?.();
|
|
1533
|
+
return true;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
type ComponentRowHit = { component: any; row: number };
|
|
1537
|
+
|
|
1538
|
+
/** hover 与点击共用组件定位;同一布局下按容器/宽度/行缓存,避免 motion 重复渲染。 */
|
|
1539
|
+
let fullscreenHoverCacheLayout: unknown = null;
|
|
1540
|
+
let fullscreenHoverComponentCache = new WeakMap<object, Map<string, ComponentRowHit | null>>();
|
|
1541
|
+
|
|
1542
|
+
function cachedFullscreenComponentAtRow(
|
|
1543
|
+
layout: any,
|
|
1544
|
+
container: any,
|
|
1545
|
+
row: number,
|
|
1546
|
+
width: number,
|
|
1547
|
+
): ComponentRowHit | null {
|
|
1548
|
+
if (!container || typeof container !== "object") return null;
|
|
1549
|
+
if (fullscreenHoverCacheLayout !== layout) {
|
|
1550
|
+
fullscreenHoverCacheLayout = layout;
|
|
1551
|
+
fullscreenHoverComponentCache = new WeakMap();
|
|
1552
|
+
}
|
|
1553
|
+
let rows = fullscreenHoverComponentCache.get(container);
|
|
1554
|
+
if (!rows) {
|
|
1555
|
+
rows = new Map();
|
|
1556
|
+
fullscreenHoverComponentCache.set(container, rows);
|
|
1557
|
+
}
|
|
1558
|
+
const key = `${width}:${row}`;
|
|
1559
|
+
if (rows.has(key)) return rows.get(key) ?? null;
|
|
1560
|
+
const hit = componentAtLocalRow(container, row, width);
|
|
1561
|
+
rows.set(key, hit);
|
|
1562
|
+
return hit;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
/** fullscreen 鼠标悬停目标。 */
|
|
1566
|
+
type FullscreenHoverTarget =
|
|
1567
|
+
| { kind: "button" }
|
|
1568
|
+
| { kind: "group"; component: ToolGroupComponent }
|
|
1569
|
+
| {
|
|
1570
|
+
kind: "tool";
|
|
1571
|
+
component: any;
|
|
1572
|
+
view: ExpandedToolIoView | null;
|
|
1573
|
+
section: ToolIoSection | null;
|
|
1574
|
+
};
|
|
1575
|
+
|
|
1576
|
+
/**
|
|
1577
|
+
* fullscreen 悬停高亮:collapsed 卡 [click to show more] hint、
|
|
1578
|
+
* expanded 卡截断头 [show more]、回到底部按钮。motion 不 consume,官方链照常。
|
|
1579
|
+
*/
|
|
1580
|
+
function handleFullscreenToolHover(tui: any, packet: SgrMousePacket): void {
|
|
1581
|
+
if (packet.final !== "M") return;
|
|
1582
|
+
const layout = tui.currentLayout;
|
|
1583
|
+
if (!layout?.root) return;
|
|
1584
|
+
const x = packet.col - 1;
|
|
1585
|
+
const y = packet.row - 1;
|
|
1586
|
+
let target: FullscreenHoverTarget | null = null;
|
|
1587
|
+
const hit = fullscreenLeafAt(layout, x, y);
|
|
1588
|
+
if (hit) {
|
|
1589
|
+
const line = hit.box.lines?.[hit.localRow];
|
|
1590
|
+
// 回到底部按钮:渲染行文本 + 列区间识别(零组件树开销)。
|
|
1591
|
+
if (typeof line === "string" && scrollButtonVisible && line.includes("[ ↓")) {
|
|
1592
|
+
const plain = stripTerminalSequencesPreservingLayout(line);
|
|
1593
|
+
const idx = plain.indexOf("[ ↓");
|
|
1594
|
+
if (idx >= 0 && x >= idx && x <= idx + plain.length - 1) {
|
|
1595
|
+
target = { kind: "button" };
|
|
1596
|
+
}
|
|
1597
|
+
} else if (typeof line === "string" && !/\x1b]8;/.test(line)) {
|
|
1598
|
+
const width = Math.max(1, Number(tui.terminal?.columns) || 80);
|
|
1599
|
+
const contentWidth =
|
|
1600
|
+
typeof hit.box.scrollView?.getContentWidth === "function"
|
|
1601
|
+
? Math.max(1, hit.box.scrollView.getContentWidth(width))
|
|
1602
|
+
: width;
|
|
1603
|
+
// 与点击共用同一定位算法,避免 hover 自建行段与真实组件树错位。
|
|
1604
|
+
const componentHit = cachedFullscreenComponentAtRow(
|
|
1605
|
+
layout,
|
|
1606
|
+
hit.box.component,
|
|
1607
|
+
hit.localRow,
|
|
1608
|
+
contentWidth,
|
|
1609
|
+
);
|
|
1610
|
+
const component = componentHit?.component;
|
|
1611
|
+
const hintBox = collapsedHintHitbox(line);
|
|
1612
|
+
const overHint = Boolean(
|
|
1613
|
+
hintBox && packet.col >= hintBox.startCol && packet.col <= hintBox.endCol,
|
|
1614
|
+
);
|
|
1615
|
+
if (component instanceof ToolGroupComponent) {
|
|
1616
|
+
if (overHint) target = { kind: "group", component };
|
|
1617
|
+
} else if (isToolExecutionComponent(component)) {
|
|
1618
|
+
let view: ExpandedToolIoView | null = null;
|
|
1619
|
+
let section: ToolIoSection | null = null;
|
|
1620
|
+
if (component.expanded) {
|
|
1621
|
+
const resultView = component.resultRendererComponent;
|
|
1622
|
+
if (isExpandedToolIoView(resultView)) {
|
|
1623
|
+
view = resultView;
|
|
1624
|
+
const plain = stripTerminalSequencesPreservingLayout(line);
|
|
1625
|
+
const candidate = view.matchShowMoreLine(plain);
|
|
1626
|
+
if (candidate) {
|
|
1627
|
+
const box = view.showMoreHitbox(plain);
|
|
1628
|
+
if (box && x + 1 >= box.startCol && x + 1 <= box.endCol) {
|
|
1629
|
+
section = candidate;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
target = { kind: "tool", component, view, section };
|
|
1634
|
+
} else if (overHint) {
|
|
1635
|
+
target = { kind: "tool", component, view, section };
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
applyFullscreenHover(tui, target);
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
/** 悬停状态变化才触发渲染(motion 事件密集,状态不变跳过)。 */
|
|
1644
|
+
function applyFullscreenHover(tui: any, target: FullscreenHoverTarget | null): void {
|
|
1645
|
+
let changed = false;
|
|
1646
|
+
const nextCallId =
|
|
1647
|
+
target?.kind === "tool" && !target.component.expanded
|
|
1648
|
+
? (target.component.toolCallId ?? null)
|
|
1649
|
+
: null;
|
|
1650
|
+
if (nextCallId !== hoveredToolCallId) {
|
|
1651
|
+
hoveredToolCallId = nextCallId;
|
|
1652
|
+
changed = true;
|
|
1653
|
+
}
|
|
1654
|
+
const nextGroup = target?.kind === "group" ? target.component : null;
|
|
1655
|
+
if (setHoveredToolGroup(nextGroup)) changed = true;
|
|
1656
|
+
const nextView = target?.kind === "tool" ? target.view : null;
|
|
1657
|
+
const nextSection = target?.kind === "tool" ? target.section : null;
|
|
1658
|
+
if (setHoveredToolIo(nextView, nextSection)) changed = true;
|
|
1659
|
+
const nextButton = target?.kind === "button";
|
|
1660
|
+
if (nextButton !== scrollButtonHovered) {
|
|
1661
|
+
scrollButtonHovered = nextButton;
|
|
1662
|
+
changed = true;
|
|
1663
|
+
}
|
|
1664
|
+
if (changed) tui.requestRender?.();
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
/**
|
|
1668
|
+
* 实例级包装 TuiAltScreen.handleViewportInput(惰性 Proxy 安全):
|
|
1669
|
+
* 原型方法取 original(绕开 proxy 函数包装),实例 own property 装 wrapper
|
|
1670
|
+
* (constructor arrow 动态查找命中)。仅在 fullscreen 且无 overlay 时先消费
|
|
1671
|
+
* 工具卡左键点击,其余全部放行官方 selection/scrollbar/URL/键盘链。
|
|
1672
|
+
*/
|
|
1673
|
+
function patchFullscreenViewportInput(tui: any): void {
|
|
1674
|
+
if (tui[FULLSCREEN_VIEWPORT_PATCH] || !isLazyProxyTui(tui)) return;
|
|
1675
|
+
const proto = Object.getPrototypeOf(tui);
|
|
1676
|
+
const original = proto?.handleViewportInput;
|
|
1677
|
+
if (typeof original !== "function") return;
|
|
1678
|
+
// 官方原生 routeWheel 已完整处理嵌套 ScrollView;只调整默认步进 1 → 3。
|
|
1679
|
+
if (typeof tui.wheelScrollLines === "number") {
|
|
1680
|
+
tui[FULLSCREEN_WHEEL_SCROLL_ORIGINAL] = tui.wheelScrollLines;
|
|
1681
|
+
tui.wheelScrollLines = FULLSCREEN_WHEEL_SCROLL_LINES;
|
|
1682
|
+
}
|
|
1683
|
+
tui[FULLSCREEN_VIEWPORT_PATCH] = true;
|
|
1684
|
+
tui.handleViewportInput = function (this: any, data: string) {
|
|
1685
|
+
if (toolMouseInteractionActive() && tui.mode === "fullscreen") {
|
|
1686
|
+
// 滚动输入(wheel/pageUp/end 等)后同步回到底部按钮显隐;
|
|
1687
|
+
// 官方 viewport 会消费键盘,扩展监听器无法补偿,必须在这里调度。
|
|
1688
|
+
scheduleScrollButtonSync(tui, data);
|
|
1537
1689
|
const packets = parseSgrMousePackets(data);
|
|
1538
|
-
|
|
1690
|
+
// 官方 fullscreen 会消费全部鼠标;文本预览 overlay 活动时放行给 focused
|
|
1691
|
+
// custom component,使 [esc] 点击和滚轮可用。
|
|
1692
|
+
if (packets && tui.hasOverlay?.() && hasActiveTextPreview()) return undefined;
|
|
1693
|
+
if (packets && !tui.hasOverlay?.()) {
|
|
1539
1694
|
for (const packet of packets) {
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
if (
|
|
1544
|
-
|
|
1695
|
+
if (isSgrLeftPress(packet) && handleFullscreenToolClick(tui, packet)) {
|
|
1696
|
+
return { consume: true };
|
|
1697
|
+
}
|
|
1698
|
+
if ((packet.code & 32) !== 0 && packet.final === "M") {
|
|
1699
|
+
handleFullscreenToolHover(tui, packet);
|
|
1700
|
+
}
|
|
1545
1701
|
}
|
|
1546
1702
|
}
|
|
1547
1703
|
}
|
|
1548
|
-
|
|
1549
|
-
typeof data === "string" && useFixedEditorFeatures(this) ? wheelDirection(data) : null;
|
|
1550
|
-
const dispatchCount = direction ? fixedEditorWheelDispatchCount(direction) : 1;
|
|
1551
|
-
let result = Reflect.apply(originalHandle, this, args);
|
|
1552
|
-
for (let index = 1; index < dispatchCount; index++) {
|
|
1553
|
-
result = Reflect.apply(originalHandle, this, args);
|
|
1554
|
-
}
|
|
1555
|
-
if (typeof data === "string") scheduleScrollButtonSync(this, data);
|
|
1556
|
-
return result;
|
|
1704
|
+
return Reflect.apply(original, this, [data]);
|
|
1557
1705
|
};
|
|
1558
|
-
|
|
1559
|
-
try {
|
|
1560
|
-
tui.handleInput = wrapper;
|
|
1561
|
-
} catch {
|
|
1562
|
-
return;
|
|
1563
|
-
}
|
|
1564
|
-
toolMouseInputPatchTui = tui;
|
|
1565
|
-
toolMouseInputPatchOriginalHandle = originalHandle;
|
|
1566
|
-
toolMouseInputPatchWrapper = wrapper;
|
|
1567
1706
|
}
|
|
1568
1707
|
|
|
1569
|
-
function
|
|
1570
|
-
if (
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
) {
|
|
1575
|
-
toolMouseInputPatchTui.handleInput = toolMouseInputPatchOriginalHandle;
|
|
1708
|
+
function restoreFullscreenViewportInput(tui: any): void {
|
|
1709
|
+
if (!tui || !tui[FULLSCREEN_VIEWPORT_PATCH]) return;
|
|
1710
|
+
const proto = Object.getPrototypeOf(tui);
|
|
1711
|
+
if (typeof proto?.handleViewportInput === "function") {
|
|
1712
|
+
tui.handleViewportInput = proto.handleViewportInput;
|
|
1576
1713
|
}
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1714
|
+
const originalWheelScrollLines = tui[FULLSCREEN_WHEEL_SCROLL_ORIGINAL];
|
|
1715
|
+
if (typeof originalWheelScrollLines === "number") {
|
|
1716
|
+
tui.wheelScrollLines = originalWheelScrollLines;
|
|
1717
|
+
tui[FULLSCREEN_WHEEL_SCROLL_ORIGINAL] = undefined;
|
|
1718
|
+
}
|
|
1719
|
+
tui[FULLSCREEN_VIEWPORT_PATCH] = false;
|
|
1580
1720
|
}
|
|
1581
1721
|
|
|
1582
1722
|
function restoreToolMouseRenderPatch(): void {
|
|
@@ -1602,16 +1742,10 @@ function buildInteractionFrame(
|
|
|
1602
1742
|
placements: FrameToolPlacement[],
|
|
1603
1743
|
): InteractionFrame {
|
|
1604
1744
|
const width = Math.max(1, Number(tui?.terminal?.columns) || 80);
|
|
1605
|
-
const fixed = useFixedEditorFeatures(tui);
|
|
1606
|
-
const viewport = fixed ? getFixedEditorViewport(tui) : null;
|
|
1607
|
-
// fixed-editor: tui.render already returned the visible root slice (screen rows).
|
|
1608
1745
|
// native: full buffer; map with the post-doRender previousViewportTop.
|
|
1609
1746
|
const lineIndexToScreenRow = (lineIndex: number) =>
|
|
1610
|
-
|
|
1611
|
-
const visibleRows =
|
|
1612
|
-
? (viewport?.visibleLines.length ??
|
|
1613
|
-
(Array.isArray(tui?.previousLines) ? tui.previousLines.length : Number.POSITIVE_INFINITY))
|
|
1614
|
-
: Math.max(1, Number(tui?.terminal?.rows) || Number.POSITIVE_INFINITY);
|
|
1747
|
+
lineIndex - (Number(tui?.previousViewportTop) || 0) + 1;
|
|
1748
|
+
const visibleRows = Math.max(1, Number(tui?.terminal?.rows) || Number.POSITIVE_INFINITY);
|
|
1615
1749
|
const regions: InteractionRegion[] = [];
|
|
1616
1750
|
const renderedByComponent = new Map<any, FrameToolRender>();
|
|
1617
1751
|
for (const rendered of renderedTools) renderedByComponent.set(rendered.component, rendered);
|
|
@@ -1675,13 +1809,74 @@ function buildInteractionFrame(
|
|
|
1675
1809
|
}
|
|
1676
1810
|
}
|
|
1677
1811
|
}
|
|
1678
|
-
const scrollHitbox = getFixedEditorScrollButtonHitbox();
|
|
1679
|
-
if (scrollButtonVisible && useFixedEditorFeatures(tui) && scrollHitbox) {
|
|
1680
|
-
regions.push({ kind: "scroll-bottom", ...scrollHitbox });
|
|
1681
|
-
}
|
|
1682
1812
|
return { regions };
|
|
1683
1813
|
}
|
|
1684
1814
|
|
|
1815
|
+
/**
|
|
1816
|
+
* 临时包装 outermost 工具/组件的 render 注入零宽 marker,返回待 restore 列表。
|
|
1817
|
+
* 调用方必须用 restoreRenderOverride 立即还原(同一次渲染内有效)。
|
|
1818
|
+
*/
|
|
1819
|
+
function wrapToolRendersForFrame(
|
|
1820
|
+
outermost: any[],
|
|
1821
|
+
renderedTools: FrameToolRender[],
|
|
1822
|
+
idToComponent: Map<number, any>,
|
|
1823
|
+
): Array<{ target: any; descriptor?: PropertyDescriptor }> {
|
|
1824
|
+
const restores: Array<{ target: any; descriptor?: PropertyDescriptor }> = [];
|
|
1825
|
+
let nextId = 0;
|
|
1826
|
+
try {
|
|
1827
|
+
for (const component of outermost) {
|
|
1828
|
+
const originalRender = component.render;
|
|
1829
|
+
if (typeof originalRender !== "function") continue;
|
|
1830
|
+
const id = nextId++;
|
|
1831
|
+
idToComponent.set(id, component);
|
|
1832
|
+
const wrappedRender = function (this: any, ...renderArgs: any[]) {
|
|
1833
|
+
let contentBoxLines = 0;
|
|
1834
|
+
const box = component.contentBox;
|
|
1835
|
+
let boxRestore: { target: any; descriptor?: PropertyDescriptor } | undefined;
|
|
1836
|
+
if (
|
|
1837
|
+
box &&
|
|
1838
|
+
Array.isArray(component.children) &&
|
|
1839
|
+
component.children.includes(box) &&
|
|
1840
|
+
typeof box.render === "function"
|
|
1841
|
+
) {
|
|
1842
|
+
const boxOriginal = box.render;
|
|
1843
|
+
const boxWrapped = function (this: any, ...boxArgs: any[]) {
|
|
1844
|
+
const boxLines = Reflect.apply(boxOriginal, this, boxArgs);
|
|
1845
|
+
if (Array.isArray(boxLines)) contentBoxLines = boxLines.length;
|
|
1846
|
+
return boxLines;
|
|
1847
|
+
};
|
|
1848
|
+
const boxDescriptor = defineRenderOverride(box, boxWrapped);
|
|
1849
|
+
if (boxDescriptor !== undefined || box.render === boxWrapped) {
|
|
1850
|
+
boxRestore = { target: box, descriptor: boxDescriptor };
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
try {
|
|
1854
|
+
const lines = Reflect.apply(originalRender, this, renderArgs);
|
|
1855
|
+
if (!Array.isArray(lines)) return lines;
|
|
1856
|
+
renderedTools.push({
|
|
1857
|
+
component,
|
|
1858
|
+
lines: lines.map((line) => String(line)),
|
|
1859
|
+
contentBoxLines,
|
|
1860
|
+
});
|
|
1861
|
+
return lines.map((line, row) => `${line}${toolFrameMarker(id, row)}`);
|
|
1862
|
+
} finally {
|
|
1863
|
+
if (boxRestore) restoreRenderOverride(boxRestore.target, boxRestore.descriptor);
|
|
1864
|
+
}
|
|
1865
|
+
};
|
|
1866
|
+
const descriptor = defineRenderOverride(component, wrappedRender);
|
|
1867
|
+
if (descriptor !== undefined || component.render === wrappedRender) {
|
|
1868
|
+
restores.push({ target: component, descriptor });
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
return restores;
|
|
1872
|
+
} catch (error) {
|
|
1873
|
+
for (const { target, descriptor } of restores.reverse()) {
|
|
1874
|
+
restoreRenderOverride(target, descriptor);
|
|
1875
|
+
}
|
|
1876
|
+
throw error;
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1685
1880
|
function defineRenderOverride(
|
|
1686
1881
|
target: any,
|
|
1687
1882
|
wrapped: (...args: any[]) => any,
|
|
@@ -1743,54 +1938,9 @@ function patchToolMouseMotionAfterRender(tui: any): void {
|
|
|
1743
1938
|
idToView: new Map(),
|
|
1744
1939
|
nextId: 0,
|
|
1745
1940
|
};
|
|
1746
|
-
const restores: Array<{ target: any; descriptor?: PropertyDescriptor }> = [];
|
|
1747
1941
|
const outermost: any[] = [];
|
|
1748
1942
|
collectToolComponents(this, outermost);
|
|
1749
|
-
|
|
1750
|
-
for (const component of outermost) {
|
|
1751
|
-
const originalRender = component.render;
|
|
1752
|
-
if (typeof originalRender !== "function") continue;
|
|
1753
|
-
const id = nextId++;
|
|
1754
|
-
idToComponent.set(id, component);
|
|
1755
|
-
const wrappedRender = function (this: any, ...renderArgs: any[]) {
|
|
1756
|
-
let contentBoxLines = 0;
|
|
1757
|
-
const box = component.contentBox;
|
|
1758
|
-
let boxRestore: { target: any; descriptor?: PropertyDescriptor } | undefined;
|
|
1759
|
-
if (
|
|
1760
|
-
box &&
|
|
1761
|
-
Array.isArray(component.children) &&
|
|
1762
|
-
component.children.includes(box) &&
|
|
1763
|
-
typeof box.render === "function"
|
|
1764
|
-
) {
|
|
1765
|
-
const boxOriginal = box.render;
|
|
1766
|
-
const boxWrapped = function (this: any, ...boxArgs: any[]) {
|
|
1767
|
-
const boxLines = Reflect.apply(boxOriginal, this, boxArgs);
|
|
1768
|
-
if (Array.isArray(boxLines)) contentBoxLines = boxLines.length;
|
|
1769
|
-
return boxLines;
|
|
1770
|
-
};
|
|
1771
|
-
const boxDescriptor = defineRenderOverride(box, boxWrapped);
|
|
1772
|
-
if (boxDescriptor !== undefined || box.render === boxWrapped) {
|
|
1773
|
-
boxRestore = { target: box, descriptor: boxDescriptor };
|
|
1774
|
-
}
|
|
1775
|
-
}
|
|
1776
|
-
try {
|
|
1777
|
-
const lines = Reflect.apply(originalRender, this, renderArgs);
|
|
1778
|
-
if (!Array.isArray(lines)) return lines;
|
|
1779
|
-
renderedTools.push({
|
|
1780
|
-
component,
|
|
1781
|
-
lines: lines.map((line) => String(line)),
|
|
1782
|
-
contentBoxLines,
|
|
1783
|
-
});
|
|
1784
|
-
return lines.map((line, row) => `${line}${toolFrameMarker(id, row)}`);
|
|
1785
|
-
} finally {
|
|
1786
|
-
if (boxRestore) restoreRenderOverride(boxRestore.target, boxRestore.descriptor);
|
|
1787
|
-
}
|
|
1788
|
-
};
|
|
1789
|
-
const descriptor = defineRenderOverride(component, wrappedRender);
|
|
1790
|
-
if (descriptor !== undefined || component.render === wrappedRender) {
|
|
1791
|
-
restores.push({ target: component, descriptor });
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1943
|
+
const restores = wrapToolRendersForFrame(outermost, renderedTools, idToComponent);
|
|
1794
1944
|
let placements: FrameToolPlacement[] = [];
|
|
1795
1945
|
const originalTuiRender = typeof this.render === "function" ? this.render : null;
|
|
1796
1946
|
let tuiRenderDescriptor: PropertyDescriptor | undefined;
|
|
@@ -1826,8 +1976,7 @@ function patchToolMouseMotionAfterRender(tui: any): void {
|
|
|
1826
1976
|
this.previousLines = extracted.lines;
|
|
1827
1977
|
placements = extracted.placements;
|
|
1828
1978
|
}
|
|
1829
|
-
if (
|
|
1830
|
-
toolMouseRawWrite?.(TOOL_MOUSE_MOTION_ENABLE);
|
|
1979
|
+
if (toolMouseInteractionActive()) toolMouseRawWrite?.(TOOL_MOUSE_MOTION_ENABLE);
|
|
1831
1980
|
return result;
|
|
1832
1981
|
} finally {
|
|
1833
1982
|
activeIoViewFrame = previousFrame;
|
|
@@ -1850,31 +1999,23 @@ function patchToolMouseMotionAfterRender(tui: any): void {
|
|
|
1850
1999
|
toolMouseRenderPatchOriginal = original;
|
|
1851
2000
|
toolMouseRenderPatchWrapper = wrapper;
|
|
1852
2001
|
toolMouseRenderPatchState = patchState;
|
|
1853
|
-
if (
|
|
1854
|
-
toolMouseRawWrite?.(TOOL_MOUSE_MOTION_ENABLE);
|
|
2002
|
+
if (toolMouseInteractionActive()) toolMouseRawWrite?.(TOOL_MOUSE_MOTION_ENABLE);
|
|
1855
2003
|
}
|
|
1856
2004
|
|
|
1857
2005
|
function handleToolMouseInput(data: string): { consume: true } | undefined {
|
|
1858
2006
|
if (!toolMouseTui) return undefined;
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
if (useFixedEditorFeatures(toolMouseTui) && jumpToBottomWithoutSubmit(toolMouseTui)) {
|
|
1867
|
-
return { consume: true };
|
|
1868
|
-
}
|
|
1869
|
-
if (!toolMouseFixedFeaturesEnabled) {
|
|
1870
|
-
// Native Pi scrolls through terminal history rather than an internal
|
|
1871
|
-
// viewport. A harmless terminal write makes Ctrl+End snap that history
|
|
1872
|
-
// to the active cursor without enabling mouse reporting.
|
|
1873
|
-
toolMouseTui.terminal?.write?.("\x1b[0m");
|
|
1874
|
-
toolMouseTui.requestRender?.();
|
|
2007
|
+
// 惰性 Proxy fullscreen:鼠标由 handleViewportInput 包装消费(官方链之前),
|
|
2008
|
+
// 此处只处理键盘(鼠标事件在官方 listener 已被 consume,到不了这里)。
|
|
2009
|
+
if (fullscreenLazyTui(toolMouseTui)) {
|
|
2010
|
+
scheduleScrollButtonSync(toolMouseTui, data);
|
|
2011
|
+
if (isScrollBottomInput(data)) {
|
|
2012
|
+
toolMouseTui.scrollToBottom?.();
|
|
2013
|
+
hideScrollButton(toolMouseTui);
|
|
1875
2014
|
return { consume: true };
|
|
1876
2015
|
}
|
|
2016
|
+
return undefined;
|
|
1877
2017
|
}
|
|
2018
|
+
updateScrollButtonFromInput(toolMouseTui, data);
|
|
1878
2019
|
// Off mode restores native input: wheel keeps scrolling through Pi's normal
|
|
1879
2020
|
// dispatcher, while hover/click affordances are entirely inactive.
|
|
1880
2021
|
if (!toolMouseInteractionActive()) return undefined;
|
|
@@ -1888,10 +2029,7 @@ function handleToolMouseInput(data: string): { consume: true } | undefined {
|
|
|
1888
2029
|
for (const packet of packets) {
|
|
1889
2030
|
updateToolSummaryHover(toolMouseTui, packet);
|
|
1890
2031
|
if (!isSgrLeftPress(packet)) continue;
|
|
1891
|
-
if (
|
|
1892
|
-
handleScrollButtonClick(toolMouseTui, packet) ||
|
|
1893
|
-
toggleToolAtMouseClick(toolMouseTui, packet)
|
|
1894
|
-
) {
|
|
2032
|
+
if (toggleToolAtMouseClick(toolMouseTui, packet)) {
|
|
1895
2033
|
consumed = true;
|
|
1896
2034
|
}
|
|
1897
2035
|
}
|
|
@@ -1902,7 +2040,7 @@ function handleToolMouseInput(data: string): { consume: true } | undefined {
|
|
|
1902
2040
|
return consumed ? { consume: true } : undefined;
|
|
1903
2041
|
}
|
|
1904
2042
|
|
|
1905
|
-
function teardownToolMouseInteraction(
|
|
2043
|
+
function teardownToolMouseInteraction(): void {
|
|
1906
2044
|
if (sessionRenderTimer) {
|
|
1907
2045
|
clearTimeout(sessionRenderTimer);
|
|
1908
2046
|
sessionRenderTimer = null;
|
|
@@ -1913,7 +2051,8 @@ function teardownToolMouseInteraction(nextFixedEditorFeatures = false): void {
|
|
|
1913
2051
|
setHoveredToolGroup(null);
|
|
1914
2052
|
setHoveredToolIo(null, null);
|
|
1915
2053
|
try {
|
|
1916
|
-
|
|
2054
|
+
// 惰性 Proxy 不启用任何 reporting(regular 会破坏终端回滚,fullscreen 归官方)。
|
|
2055
|
+
if (!isLazyProxyTui(toolMouseTui)) {
|
|
1917
2056
|
toolMouseTui?.terminal?.write?.(TOOL_MOUSE_DISABLE);
|
|
1918
2057
|
}
|
|
1919
2058
|
} catch {
|
|
@@ -1924,54 +2063,45 @@ function teardownToolMouseInteraction(nextFixedEditorFeatures = false): void {
|
|
|
1924
2063
|
} catch {
|
|
1925
2064
|
// The UI context may already have been reset during /reload.
|
|
1926
2065
|
}
|
|
1927
|
-
restoreToolMouseInputCapture();
|
|
1928
2066
|
restoreToolMouseRenderPatch();
|
|
2067
|
+
restoreFullscreenViewportInput(toolMouseTui);
|
|
1929
2068
|
scrollButtonVisible = false;
|
|
1930
2069
|
scrollButtonHovered = false;
|
|
1931
2070
|
scrollButtonWidget = null;
|
|
1932
|
-
pendingScrollMessages = 0;
|
|
1933
|
-
assistantMessageActive = false;
|
|
1934
2071
|
scrollButtonSyncScheduled = false;
|
|
1935
2072
|
toolMouseTui = null;
|
|
1936
2073
|
toolMouseUi = null;
|
|
1937
|
-
toolMouseFixedFeaturesEnabled = false;
|
|
1938
|
-
wheelExtraRowRemainder = 0;
|
|
1939
|
-
lastWheelDirection = null;
|
|
1940
|
-
collapseCompensationRemainder = 0;
|
|
1941
2074
|
}
|
|
1942
2075
|
|
|
1943
|
-
export function installToolMouseInteraction(
|
|
1944
|
-
|
|
1945
|
-
fixedEditorFeatures = config.fixedEditorFeatures,
|
|
1946
|
-
): void {
|
|
1947
|
-
teardownToolMouseInteraction(fixedEditorFeatures);
|
|
2076
|
+
export function installToolMouseInteraction(ctx: any): void {
|
|
2077
|
+
teardownToolMouseInteraction();
|
|
1948
2078
|
if (ctx?.mode !== "tui" || !ctx?.hasUI) return;
|
|
1949
2079
|
if (typeof ctx.ui?.onTerminalInput !== "function" || typeof ctx.ui?.setWidget !== "function")
|
|
1950
2080
|
return;
|
|
1951
2081
|
|
|
1952
2082
|
toolMouseUi = ctx.ui;
|
|
1953
|
-
|
|
1954
|
-
//
|
|
1955
|
-
// fixed-editor compositor 亦停用 —— 整个工具鼠标交互体系(hover/点击/滚动按钮)
|
|
1956
|
-
// 让位给官方,连 onTerminalInput 监听都不注册。
|
|
1957
|
-
let proxyTui = false;
|
|
2083
|
+
// 0.84+ 的 tui 是惰性 Proxy:行级 patch(doRender/handleInput)无法安全捕获。
|
|
2084
|
+
// regular 模式不启用鼠标上报(保终端回滚);fullscreen 由官方 viewport 接管。
|
|
1958
2085
|
ctx.ui.setWidget(TOOL_MOUSE_WIDGET_KEY, (tui: any, theme: any) => {
|
|
1959
2086
|
toolMouseTui = tui;
|
|
1960
2087
|
if (isLazyProxyTui(tui)) {
|
|
1961
|
-
|
|
1962
|
-
|
|
2088
|
+
// 惰性 Proxy:不启用任何 mouse reporting(regular 会破坏终端回滚,
|
|
2089
|
+
// fullscreen 官方已启用)。fullscreen 工具卡点击/回到底部按钮由
|
|
2090
|
+
// handleViewportInput 实例包装提供;widget render 时重检查 renderer
|
|
2091
|
+
// 替换(switchTuiMode 新建实例后重新安装包装)。
|
|
2092
|
+
patchFullscreenViewportInput(tui);
|
|
2093
|
+
scrollButtonWidget = {
|
|
2094
|
+
render: (width: number) => {
|
|
2095
|
+
patchFullscreenViewportInput(tui);
|
|
2096
|
+
return renderScrollButton(width, theme);
|
|
2097
|
+
},
|
|
2098
|
+
invalidate() {},
|
|
2099
|
+
};
|
|
2100
|
+
return scrollButtonWidget;
|
|
1963
2101
|
}
|
|
1964
|
-
//
|
|
1965
|
-
// once the fixed-editor compositor actually owns the terminal.
|
|
1966
|
-
if (useFixedEditorFeatures(tui)) patchToolMouseInputCapture(tui);
|
|
1967
|
-
// Wrap doRender unconditionally: the fixed-editor rebuild replaces the
|
|
1968
|
-
// chain (setBeforeFixedEditorStart restores it first) and onRebuild
|
|
1969
|
-
// re-applies this wrapper once the compositor owns the terminal.
|
|
2102
|
+
// Wrap doRender to capture the live frame for tool click/hover mapping.
|
|
1970
2103
|
patchToolMouseMotionAfterRender(tui);
|
|
1971
|
-
|
|
1972
|
-
// while mouse interaction is active (rendering mode on + fixed editor).
|
|
1973
|
-
if (!fixedEditorFeatures && toolMouseInteractionActive())
|
|
1974
|
-
tui?.terminal?.write?.(TOOL_MOUSE_MOTION_ENABLE);
|
|
2104
|
+
if (toolMouseInteractionActive()) tui?.terminal?.write?.(TOOL_MOUSE_MOTION_ENABLE);
|
|
1975
2105
|
const widget = {
|
|
1976
2106
|
render: (width: number) => renderScrollButton(width, theme),
|
|
1977
2107
|
invalidate() {},
|
|
@@ -1979,10 +2109,6 @@ export function installToolMouseInteraction(
|
|
|
1979
2109
|
scrollButtonWidget = widget;
|
|
1980
2110
|
return widget;
|
|
1981
2111
|
});
|
|
1982
|
-
if (proxyTui) {
|
|
1983
|
-
teardownToolMouseInteraction(fixedEditorFeatures);
|
|
1984
|
-
return;
|
|
1985
|
-
}
|
|
1986
2112
|
toolMouseInputUnsubscribe = ctx.ui.onTerminalInput(handleToolMouseInput);
|
|
1987
2113
|
}
|
|
1988
2114
|
|
|
@@ -2002,10 +2128,7 @@ function scheduleSessionRender(refresh?: () => void): void {
|
|
|
2002
2128
|
sessionRenderTimer = setTimeout(() => {
|
|
2003
2129
|
sessionRenderTimer = null;
|
|
2004
2130
|
if (toolMouseTui !== tui) return;
|
|
2005
|
-
|
|
2006
|
-
if (!toolMouseFixedFeaturesEnabled || useFixedEditorFeatures(tui)) {
|
|
2007
|
-
patchToolMouseMotionAfterRender(tui);
|
|
2008
|
-
}
|
|
2131
|
+
patchToolMouseMotionAfterRender(tui);
|
|
2009
2132
|
refreshToolRendererComponents(tui);
|
|
2010
2133
|
refresh?.();
|
|
2011
2134
|
tui.requestRender(true);
|
|
@@ -2016,48 +2139,34 @@ function scheduleSessionRender(refresh?: () => void): void {
|
|
|
2016
2139
|
const BRIGHT_GREEN = "\x1b[38;2;80;220;100m";
|
|
2017
2140
|
const ANSI_FG_RESET = "\x1b[39m";
|
|
2018
2141
|
|
|
2019
|
-
function refreshCurrentTranscript(
|
|
2020
|
-
compactStyle: CompactStyleHooks,
|
|
2021
|
-
ctx?: any,
|
|
2022
|
-
toolGrouping?: ToolGroupingHooks,
|
|
2023
|
-
): void {
|
|
2142
|
+
function refreshCurrentTranscript(ctx?: any, toolGrouping?: ToolGroupingHooks): void {
|
|
2024
2143
|
toolGrouping?.refresh();
|
|
2025
|
-
compactStyle.refresh();
|
|
2026
2144
|
toolMouseTui?.requestRender?.(true);
|
|
2027
2145
|
ctx?.ui?.requestRender?.(true);
|
|
2028
2146
|
}
|
|
2029
2147
|
|
|
2030
|
-
function applyStyleMode(
|
|
2031
|
-
mode: CompactStyleMode,
|
|
2032
|
-
ctx: any,
|
|
2033
|
-
compactStyle: CompactStyleHooks,
|
|
2034
|
-
toolGrouping?: ToolGroupingHooks,
|
|
2035
|
-
): void {
|
|
2148
|
+
function applyStyleMode(mode: CompactStyleMode, ctx: any, toolGrouping?: ToolGroupingHooks): void {
|
|
2036
2149
|
config.mode = mode;
|
|
2037
2150
|
saveConfig();
|
|
2038
2151
|
if (mode === "off") {
|
|
2039
2152
|
// Native rendering mode: drop hover/click state and fully disable mouse
|
|
2040
2153
|
// reporting so the terminal restores its default scrollback wheel scrolling.
|
|
2041
|
-
// (The fixed-editor compositor owns its own reporting when enabled.)
|
|
2042
2154
|
hoveredToolCallId = null;
|
|
2043
2155
|
setHoveredToolGroup(null);
|
|
2044
2156
|
setHoveredToolIo(null, null);
|
|
2045
2157
|
scrollButtonVisible = false;
|
|
2046
2158
|
scrollButtonHovered = false;
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
if (toolMouseTui && !
|
|
2159
|
+
// 惰性 Proxy(regular/fullscreen)不持有 reporting:regular 保终端回滚,
|
|
2160
|
+
// fullscreen 归官方所有,均不能在此关闭。
|
|
2161
|
+
if (toolMouseTui && !isLazyProxyTui(toolMouseTui)) {
|
|
2050
2162
|
toolMouseTui.terminal?.write?.(TOOL_MOUSE_DISABLE);
|
|
2051
2163
|
}
|
|
2052
2164
|
}
|
|
2053
|
-
refreshCurrentTranscript(
|
|
2165
|
+
refreshCurrentTranscript(ctx, toolGrouping);
|
|
2054
2166
|
ctx.ui.notify(`Claude Code style: ${mode}`, "info");
|
|
2055
2167
|
}
|
|
2056
2168
|
|
|
2057
2169
|
function modeSettingDescription(mode: CompactStyleMode): string {
|
|
2058
|
-
if (mode === "compact") {
|
|
2059
|
-
return "Compact transcript summaries. Diff options below still apply independently.";
|
|
2060
|
-
}
|
|
2061
2170
|
if (mode === "off") {
|
|
2062
2171
|
return "Pi native tool rendering. Diff options below still apply independently.";
|
|
2063
2172
|
}
|
|
@@ -2099,7 +2208,7 @@ function buildExcludeRenderersSubmenu(
|
|
|
2099
2208
|
description:
|
|
2100
2209
|
name === "Agent"
|
|
2101
2210
|
? "Agent always uses its dedicated renderer and cannot be forced through ccstyle."
|
|
2102
|
-
: `Use Pi native renderer for ${name} instead of Claude Code
|
|
2211
|
+
: `Use Pi native renderer for ${name} instead of Claude Code styling.`,
|
|
2103
2212
|
currentValue: config.excludeRenderers.includes(name) ? "exclude" : "style",
|
|
2104
2213
|
values: ["style", "exclude"],
|
|
2105
2214
|
}));
|
|
@@ -2171,7 +2280,6 @@ function renderSectionTabBar(
|
|
|
2171
2280
|
|
|
2172
2281
|
async function showCcstylePanel(
|
|
2173
2282
|
ctx: any,
|
|
2174
|
-
compactStyle: CompactStyleHooks,
|
|
2175
2283
|
toolGrouping?: ToolGroupingHooks,
|
|
2176
2284
|
compactThinking?: CompactThinkingController,
|
|
2177
2285
|
): Promise<void> {
|
|
@@ -2186,7 +2294,7 @@ async function showCcstylePanel(
|
|
|
2186
2294
|
label: "Mode",
|
|
2187
2295
|
description: modeSettingDescription(config.mode),
|
|
2188
2296
|
currentValue: config.mode,
|
|
2189
|
-
values: ["on", "off"
|
|
2297
|
+
values: ["on", "off"],
|
|
2190
2298
|
};
|
|
2191
2299
|
// Tracks whether the Exclude-tools submenu is open so Tab switches sections
|
|
2192
2300
|
// only at the top level (mirrors Zentui settings: Tab = switch sections).
|
|
@@ -2208,7 +2316,7 @@ async function showCcstylePanel(
|
|
|
2208
2316
|
() => {
|
|
2209
2317
|
excludeSetting.currentValue = formatExcludeRenderers(config.excludeRenderers);
|
|
2210
2318
|
excludeSetting.description = excludeRenderersDescription(config.excludeRenderers);
|
|
2211
|
-
refreshCurrentTranscript(
|
|
2319
|
+
refreshCurrentTranscript(ctx);
|
|
2212
2320
|
},
|
|
2213
2321
|
);
|
|
2214
2322
|
},
|
|
@@ -2287,7 +2395,7 @@ async function showCcstylePanel(
|
|
|
2287
2395
|
switch (id) {
|
|
2288
2396
|
case "mode":
|
|
2289
2397
|
modeSetting.description = modeSettingDescription(value as CompactStyleMode);
|
|
2290
|
-
applyStyleMode(value as CompactStyleMode, ctx,
|
|
2398
|
+
applyStyleMode(value as CompactStyleMode, ctx, toolGrouping);
|
|
2291
2399
|
return;
|
|
2292
2400
|
case "excludeRenderers":
|
|
2293
2401
|
excludeSetting.currentValue = formatExcludeRenderers(config.excludeRenderers);
|
|
@@ -2348,7 +2456,7 @@ async function showCcstylePanel(
|
|
|
2348
2456
|
}
|
|
2349
2457
|
saveConfig();
|
|
2350
2458
|
compactThinking?.updateConfig(getCompactThinkingConfig());
|
|
2351
|
-
refreshCurrentTranscript(
|
|
2459
|
+
refreshCurrentTranscript(ctx);
|
|
2352
2460
|
ctx.ui.notify(`Updated ${id}: ${value}`, "info");
|
|
2353
2461
|
};
|
|
2354
2462
|
|
|
@@ -2761,8 +2869,9 @@ function createCcstyleTool(
|
|
|
2761
2869
|
? `${outputLines} ${lineWord} ${action}`
|
|
2762
2870
|
: "Done";
|
|
2763
2871
|
const expandable = !expanded && (tasks.length > 0 || hasExpandableDetail(text, args));
|
|
2764
|
-
const
|
|
2765
|
-
const
|
|
2872
|
+
const hintPrefix = expandable ? theme.fg("muted", " • ") : "";
|
|
2873
|
+
const hint = expandable ? hintPrefix + theme.fg("muted", "click to show more") : "";
|
|
2874
|
+
const hoveredHint = expandable ? hintPrefix + theme.fg("text", "click to show more") : "";
|
|
2766
2875
|
if (expanded) {
|
|
2767
2876
|
return renderExpandedToolResult(
|
|
2768
2877
|
text || "",
|
|
@@ -2801,7 +2910,7 @@ function createCcstyleTool(
|
|
|
2801
2910
|
/**
|
|
2802
2911
|
* Apart from the write override used to capture pre-write content, renderers are
|
|
2803
2912
|
* applied through ToolExecutionComponent. Patch its lookup once so tools use the
|
|
2804
|
-
* same
|
|
2913
|
+
* same ccstyle fallback shell by default. Tools named in excludeRenderers keep
|
|
2805
2914
|
* their original renderer.
|
|
2806
2915
|
*/
|
|
2807
2916
|
const GLOBAL_TOOL_RENDER_PATCH = Symbol.for("pi.ccstyle.global-tool-render-patch");
|
|
@@ -3128,18 +3237,6 @@ function deactivateLegacyCompactionRendering() {
|
|
|
3128
3237
|
if (patch) patch.enabled = () => false;
|
|
3129
3238
|
}
|
|
3130
3239
|
|
|
3131
|
-
function notePendingScrollMessage(role: unknown): void {
|
|
3132
|
-
if (!toolMouseTui || !useFixedEditorFeatures(toolMouseTui) || !scrollButtonVisible) return;
|
|
3133
|
-
if (role === "assistant") {
|
|
3134
|
-
if (assistantMessageActive) return;
|
|
3135
|
-
assistantMessageActive = true;
|
|
3136
|
-
} else if (role !== "toolResult") {
|
|
3137
|
-
return;
|
|
3138
|
-
}
|
|
3139
|
-
pendingScrollMessages += 1;
|
|
3140
|
-
toolMouseTui.requestRender?.();
|
|
3141
|
-
}
|
|
3142
|
-
|
|
3143
3240
|
export default function (
|
|
3144
3241
|
pi: ExtensionAPI,
|
|
3145
3242
|
configOverride?: Partial<Config>,
|
|
@@ -3147,55 +3244,31 @@ export default function (
|
|
|
3147
3244
|
) {
|
|
3148
3245
|
// The optional override keeps integration tests independent from the user's global config.
|
|
3149
3246
|
if (configOverride) config = normalizeConfig({ ...config, ...configOverride });
|
|
3150
|
-
const
|
|
3151
|
-
// Unwrap mouse doRender before compositor construction captures the chain.
|
|
3152
|
-
setBeforeFixedEditorStart(() => {
|
|
3153
|
-
restoreToolMouseRenderPatch();
|
|
3154
|
-
});
|
|
3155
|
-
// Footer/compositor rebuild disposes the old chain; re-wrap the live doRender and repaint.
|
|
3156
|
-
fixedEditorController.onRebuild(() => {
|
|
3157
|
-
const tui = toolMouseTui;
|
|
3158
|
-
if (!tui) return;
|
|
3159
|
-
if (toolMouseFixedFeaturesEnabled) patchToolMouseInputCapture(tui);
|
|
3160
|
-
// compositor.install captured the current chain as originalDoRender, so an
|
|
3161
|
-
// already-installed wrapper keeps receiving calls through it — re-wrapping
|
|
3162
|
-
// only when no wrapper is active avoids stacking one per rebuild/reload.
|
|
3163
|
-
if (!(toolMouseRenderPatchTui === tui && toolMouseRenderPatchState?.active)) {
|
|
3164
|
-
patchToolMouseMotionAfterRender(tui);
|
|
3165
|
-
}
|
|
3166
|
-
tui.requestRender?.(true);
|
|
3167
|
-
});
|
|
3168
|
-
const writeExecutionMetadata = installWriteOverride(pi, new WriteExecutionMetadataStore());
|
|
3247
|
+
const writeExecutionMetadata = new WriteExecutionMetadataStore();
|
|
3169
3248
|
let installation:
|
|
3170
3249
|
| {
|
|
3171
3250
|
globalToolRendering: GlobalToolRenderPatch;
|
|
3172
3251
|
toolGrouping: ToolGroupingHooks;
|
|
3173
|
-
compactStyle: CompactStyleHooks;
|
|
3174
3252
|
}
|
|
3175
3253
|
| undefined;
|
|
3176
3254
|
const ensureTuiInstallation = (ctx: any) => {
|
|
3177
3255
|
if (ctx?.mode !== "tui" || !ctx?.hasUI) return undefined;
|
|
3178
|
-
// fullscreen
|
|
3179
|
-
|
|
3256
|
+
// 渲染层(工具样式/分组)是原型与组件级 patch,fullscreen 官方布局
|
|
3257
|
+
// 同样渲染这些组件,因此两种模式都安装。
|
|
3180
3258
|
if (installation) return installation;
|
|
3181
3259
|
const globalToolRendering = installGlobalToolRendering(writeExecutionMetadata);
|
|
3182
3260
|
const toolGrouping = installToolGrouping(() => config.mode === "on");
|
|
3183
3261
|
deactivateLegacyCompactionRendering();
|
|
3184
|
-
|
|
3185
|
-
getMode: () => config.mode,
|
|
3186
|
-
getExcludeRenderers: () => config.excludeRenderers,
|
|
3187
|
-
});
|
|
3188
|
-
installation = { globalToolRendering, toolGrouping, compactStyle };
|
|
3262
|
+
installation = { globalToolRendering, toolGrouping };
|
|
3189
3263
|
return installation;
|
|
3190
3264
|
};
|
|
3191
3265
|
|
|
3192
3266
|
pi.registerCommand("ccstyle", {
|
|
3193
|
-
description: "Configure Claude Code style
|
|
3267
|
+
description: "Configure Claude Code style and rich diff options",
|
|
3194
3268
|
getArgumentCompletions: (prefix) => {
|
|
3195
3269
|
const topLevel = [
|
|
3196
3270
|
{ value: "on", label: "on", description: "Enable Claude Code style" },
|
|
3197
3271
|
{ value: "off", label: "off", description: "Use Pi's native renderer" },
|
|
3198
|
-
{ value: "compact", label: "compact", description: "Use compact transcript rendering" },
|
|
3199
3272
|
{ value: "status", label: "status", description: "Show full configuration" },
|
|
3200
3273
|
{ value: "panel", label: "panel", description: "Open interactive settings panel" },
|
|
3201
3274
|
];
|
|
@@ -3203,24 +3276,17 @@ export default function (
|
|
|
3203
3276
|
},
|
|
3204
3277
|
handler: async (args, ctx) => {
|
|
3205
3278
|
const arg = args.trim().toLowerCase();
|
|
3206
|
-
if (!arg || arg === "panel" || arg === "on" || arg === "off"
|
|
3279
|
+
if (!arg || arg === "panel" || arg === "on" || arg === "off") {
|
|
3207
3280
|
if (ctx?.mode !== "tui" || !ctx?.hasUI) {
|
|
3208
3281
|
ctx.ui?.notify?.("/ccstyle requires TUI mode", "warning");
|
|
3209
3282
|
return;
|
|
3210
3283
|
}
|
|
3211
|
-
if (isFullscreenUi(ctx)) {
|
|
3212
|
-
ctx.ui.notify(
|
|
3213
|
-
"ccstyle 渲染已在 fullscreen 模式下让位给官方 TUI;请切换到 regular 模式后使用 /ccstyle",
|
|
3214
|
-
"warning",
|
|
3215
|
-
);
|
|
3216
|
-
return;
|
|
3217
|
-
}
|
|
3218
3284
|
const hooks = ensureTuiInstallation(ctx);
|
|
3219
3285
|
if (!hooks) return;
|
|
3220
3286
|
if (!arg || arg === "panel") {
|
|
3221
|
-
await showCcstylePanel(ctx, hooks.
|
|
3287
|
+
await showCcstylePanel(ctx, hooks.toolGrouping, compactThinking);
|
|
3222
3288
|
} else {
|
|
3223
|
-
applyStyleMode(arg, ctx, hooks.
|
|
3289
|
+
applyStyleMode(arg, ctx, hooks.toolGrouping);
|
|
3224
3290
|
}
|
|
3225
3291
|
return;
|
|
3226
3292
|
}
|
|
@@ -3228,73 +3294,44 @@ export default function (
|
|
|
3228
3294
|
ctx.ui.notify(`Claude Code style: ${formatConfigStatus(config)}`, "info");
|
|
3229
3295
|
return;
|
|
3230
3296
|
}
|
|
3231
|
-
ctx.ui.notify("Usage: /ccstyle [on|off|
|
|
3297
|
+
ctx.ui.notify("Usage: /ccstyle [on|off|status|panel]", "warning");
|
|
3232
3298
|
},
|
|
3233
3299
|
});
|
|
3234
3300
|
|
|
3235
3301
|
pi.on("session_start", async (event, ctx) => {
|
|
3302
|
+
// 延迟到 session_start 注册 write override:加载阶段 getAllTools 不可用且其他扩展
|
|
3303
|
+
// 尚未注册工具,无法检测外部 write 所有者(如 pi-spark),直接注册会与对方撞名。
|
|
3304
|
+
// session_start 时所有扩展已加载完毕,installWriteOverride 内部会检测并让位。
|
|
3305
|
+
installWriteOverride(pi, writeExecutionMetadata);
|
|
3236
3306
|
const hooks = ensureTuiInstallation(ctx);
|
|
3307
|
+
// 鼠标交互独立于渲染层:fullscreen 渲染层让位(hooks undefined)但
|
|
3308
|
+
// 工具点击/回到底部适配仍需安装;保持在渲染层安装之后以维持原顺序。
|
|
3309
|
+
if (ctx?.mode === "tui" && ctx?.hasUI) installToolMouseInteraction(ctx);
|
|
3237
3310
|
if (!hooks) return;
|
|
3238
3311
|
hooks.toolGrouping.setTheme(ctx.ui.theme);
|
|
3239
|
-
hooks.compactStyle.onSessionStart(event, ctx);
|
|
3240
|
-
pendingScrollMessages = 0;
|
|
3241
|
-
assistantMessageActive = false;
|
|
3242
3312
|
ctx.ui.setStatus("ccstyle", undefined);
|
|
3243
|
-
|
|
3244
|
-
scheduleSessionRender(hooks.compactStyle.refresh);
|
|
3313
|
+
scheduleSessionRender();
|
|
3245
3314
|
});
|
|
3246
3315
|
|
|
3247
3316
|
pi.on("session_compact", async (event, ctx) => {
|
|
3248
3317
|
const hooks = ensureTuiInstallation(ctx);
|
|
3249
|
-
if (!hooks) return;
|
|
3250
|
-
hooks.toolGrouping.setTheme(ctx.ui.theme);
|
|
3251
|
-
hooks.compactStyle.onSessionCompact(event, ctx);
|
|
3252
3318
|
// Compaction rebuilds the transcript without session_start. Rebind after
|
|
3253
3319
|
// other TUI extensions may have replaced the root input dispatcher.
|
|
3254
|
-
installToolMouseInteraction(ctx);
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
pi.on("message_start", async (event) => {
|
|
3259
|
-
if (installation) notePendingScrollMessage(event?.message?.role);
|
|
3260
|
-
});
|
|
3261
|
-
|
|
3262
|
-
pi.on("message_update", async (event, ctx) => {
|
|
3263
|
-
installation?.compactStyle.onMessageUpdate(event, ctx);
|
|
3264
|
-
if (installation && event?.message?.role === "assistant") notePendingScrollMessage("assistant");
|
|
3265
|
-
});
|
|
3266
|
-
|
|
3267
|
-
pi.on("message_end", async (event) => {
|
|
3268
|
-
if (installation && event?.message?.role === "assistant") assistantMessageActive = false;
|
|
3269
|
-
});
|
|
3270
|
-
|
|
3271
|
-
pi.on("agent_start", async (event, ctx) => {
|
|
3272
|
-
installation?.compactStyle.onAgentStart(event, ctx);
|
|
3273
|
-
});
|
|
3274
|
-
|
|
3275
|
-
pi.on("agent_end", async (event, ctx) => {
|
|
3276
|
-
installation?.compactStyle.onAgentEnd(event, ctx);
|
|
3277
|
-
});
|
|
3278
|
-
|
|
3279
|
-
pi.on("turn_start", async (event, ctx) => {
|
|
3280
|
-
installation?.compactStyle.onTurnStart(event, ctx);
|
|
3320
|
+
if (ctx?.mode === "tui" && ctx?.hasUI) installToolMouseInteraction(ctx);
|
|
3321
|
+
if (!hooks) return;
|
|
3322
|
+
hooks.toolGrouping.setTheme(ctx.ui.theme);
|
|
3323
|
+
scheduleSessionRender();
|
|
3281
3324
|
});
|
|
3282
3325
|
|
|
3283
3326
|
pi.on("tool_execution_start", async (event, ctx) => {
|
|
3284
3327
|
installation?.toolGrouping.setTheme(ctx.ui.theme);
|
|
3285
|
-
installation?.compactStyle.onToolExecutionStart(event, ctx);
|
|
3286
|
-
});
|
|
3287
|
-
|
|
3288
|
-
pi.on("tool_execution_update", async (event, ctx) => {
|
|
3289
|
-
installation?.compactStyle.onToolExecutionUpdate(event, ctx);
|
|
3290
|
-
});
|
|
3291
|
-
|
|
3292
|
-
pi.on("tool_execution_end", async (event, ctx) => {
|
|
3293
|
-
installation?.compactStyle.onToolExecutionEnd(event, ctx);
|
|
3294
3328
|
});
|
|
3295
3329
|
|
|
3296
3330
|
pi.on("session_shutdown", async (event, ctx) => {
|
|
3297
3331
|
writeExecutionMetadata.clear();
|
|
3332
|
+
// 鼠标交互独立于渲染层:fullscreen 下 installation 为 undefined,
|
|
3333
|
+
// 但 onTerminalInput 监听与 handleViewportInput 包装仍需释放。
|
|
3334
|
+
teardownToolMouseInteraction();
|
|
3298
3335
|
const current = installation;
|
|
3299
3336
|
if (
|
|
3300
3337
|
!current ||
|
|
@@ -3302,8 +3339,6 @@ export default function (
|
|
|
3302
3339
|
!current.globalToolRendering.active
|
|
3303
3340
|
)
|
|
3304
3341
|
return;
|
|
3305
|
-
current.compactStyle.onSessionShutdown(event, ctx);
|
|
3306
|
-
teardownToolMouseInteraction();
|
|
3307
3342
|
deactivateGlobalToolRendering(current.globalToolRendering);
|
|
3308
3343
|
current.toolGrouping.shutdown();
|
|
3309
3344
|
deactivateLegacyCompactionRendering();
|