pi-cc-extensions 0.8.67 → 0.8.69
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 +6 -4
- package/README.md +6 -4
- package/extensions/config/config.ts +30 -38
- package/extensions/config/panel.ts +62 -13
- package/extensions/feature/agent-summary/core.ts +5 -26
- package/extensions/feature/agent-summary/index.ts +2 -2
- package/extensions/feature/compact-thinking.ts +0 -3
- package/extensions/feature/context.ts +1 -0
- package/extensions/feature/reference/index.ts +7 -53
- package/extensions/feature/reference/session.ts +2 -14
- package/extensions/feature/shell/flush-docked-bash.ts +36 -0
- package/extensions/feature/shell/startup-header.ts +1 -6
- package/extensions/index.ts +2 -0
- package/extensions/renderer/compact-mode.ts +8 -23
- package/extensions/renderer/default-mode.ts +26 -64
- package/extensions/renderer/index.ts +0 -13
- package/extensions/renderer/mouse/hover.ts +0 -5
- package/extensions/renderer/mouse/interaction.ts +57 -27
- package/extensions/renderer/mouse/packets.ts +1 -0
- package/extensions/renderer/mouse/scroll.ts +7 -14
- package/extensions/renderer/tool/diff/diff-limits.ts +2 -10
- package/extensions/renderer/tool/diff/diff-parse.ts +2 -9
- package/extensions/renderer/tool/names.ts +1 -1
- package/extensions/renderer/tool/result.ts +45 -31
- package/extensions/utils/patch-keys.ts +5 -10
- package/extensions/utils/sgr-mouse.ts +6 -0
- package/package.json +3 -3
|
@@ -41,11 +41,8 @@ import { paddedBackgroundRow } from "./tool/grouping.ts";
|
|
|
41
41
|
import { hasVisibleText, stripBackgroundAnsi } from "../utils/ansi-text.ts";
|
|
42
42
|
import { walkComponentTree } from "../utils/component-tree.ts";
|
|
43
43
|
import {
|
|
44
|
-
ASSISTANT_REENTRY_DESCRIPTION,
|
|
45
44
|
ASSISTANT_REENTRY_KEY,
|
|
46
|
-
ASSISTANT_SET_EXPANDED_DESCRIPTION,
|
|
47
45
|
ASSISTANT_SET_EXPANDED_KEY,
|
|
48
|
-
ASSISTANT_TOGGLE_ROUND_DESCRIPTION,
|
|
49
46
|
ASSISTANT_TOGGLE_ROUND_KEY,
|
|
50
47
|
COMPACT_MODE_PATCH_KEY,
|
|
51
48
|
COMPACT_THINKING_PATCH_KEY,
|
|
@@ -433,28 +430,16 @@ function isToolComponent(value: any): boolean {
|
|
|
433
430
|
}
|
|
434
431
|
|
|
435
432
|
function detachAssistantExpansion(component: any): void {
|
|
436
|
-
|
|
437
|
-
typeof component?.[ASSISTANT_SET_EXPANDED_KEY]
|
|
438
|
-
component.setExpanded
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
owned = Object.getOwnPropertySymbols(component).some(
|
|
442
|
-
(symbol) =>
|
|
443
|
-
symbol.description === ASSISTANT_SET_EXPANDED_DESCRIPTION && component[symbol] === true,
|
|
444
|
-
);
|
|
433
|
+
if (
|
|
434
|
+
typeof component?.[ASSISTANT_SET_EXPANDED_KEY] !== "function" ||
|
|
435
|
+
component.setExpanded !== component[ASSISTANT_SET_EXPANDED_KEY]
|
|
436
|
+
) {
|
|
437
|
+
return;
|
|
445
438
|
}
|
|
446
|
-
if (!owned) return;
|
|
447
|
-
|
|
448
439
|
delete component.setExpanded;
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
symbol.description === ASSISTANT_TOGGLE_ROUND_DESCRIPTION ||
|
|
453
|
-
symbol.description === ASSISTANT_REENTRY_DESCRIPTION
|
|
454
|
-
) {
|
|
455
|
-
delete component[symbol];
|
|
456
|
-
}
|
|
457
|
-
}
|
|
440
|
+
delete component[ASSISTANT_SET_EXPANDED_KEY];
|
|
441
|
+
delete component[ASSISTANT_TOGGLE_ROUND_KEY];
|
|
442
|
+
delete component[ASSISTANT_REENTRY_KEY];
|
|
458
443
|
}
|
|
459
444
|
|
|
460
445
|
/** 供 mouse-interaction 识别可点击的 compact assistant 行(仅 compact 模式下生效)。 */
|
|
@@ -62,20 +62,13 @@ type ToolRenderMethods = {
|
|
|
62
62
|
type GlobalToolRenderPatch = {
|
|
63
63
|
version: 2;
|
|
64
64
|
prototype: any;
|
|
65
|
-
owner: object;
|
|
66
65
|
active: boolean;
|
|
67
|
-
enabled: () => boolean;
|
|
68
66
|
mode: () => CompactStyleMode;
|
|
69
67
|
wrap: (tool: any) => any;
|
|
70
68
|
byDefinition: WeakMap<object, any>;
|
|
71
69
|
byName: Map<string, any>;
|
|
72
70
|
downstream: ToolRenderMethods;
|
|
73
71
|
installed: ToolRenderMethods;
|
|
74
|
-
// 兼容旧扩展读取 Symbol 上的 legacy 字段。
|
|
75
|
-
originalHasRendererDefinition: ToolRenderMethods["hasRendererDefinition"];
|
|
76
|
-
originalGetRenderShell: ToolRenderMethods["getRenderShell"];
|
|
77
|
-
originalGetCallRenderer: ToolRenderMethods["getCallRenderer"];
|
|
78
|
-
originalGetResultRenderer: ToolRenderMethods["getResultRenderer"];
|
|
79
72
|
};
|
|
80
73
|
|
|
81
74
|
type ToolExpandedBackgroundPatch = {
|
|
@@ -450,63 +443,39 @@ function isOwnershipAwarePatch(value: any): value is GlobalToolRenderPatch {
|
|
|
450
443
|
);
|
|
451
444
|
}
|
|
452
445
|
|
|
453
|
-
function downstreamForGlobalToolInstall(
|
|
446
|
+
function downstreamForGlobalToolInstall(
|
|
447
|
+
prototype: any,
|
|
448
|
+
previous: GlobalToolRenderPatch | undefined,
|
|
449
|
+
): ToolRenderMethods {
|
|
454
450
|
const current = prototypeToolRenderMethods(prototype);
|
|
455
|
-
if (!previous || previous.prototype !== prototype)
|
|
456
|
-
|
|
457
|
-
return {
|
|
458
|
-
hasRendererDefinition:
|
|
459
|
-
current.hasRendererDefinition === previous.installed.hasRendererDefinition
|
|
460
|
-
? previous.downstream.hasRendererDefinition
|
|
461
|
-
: current.hasRendererDefinition,
|
|
462
|
-
getRenderShell:
|
|
463
|
-
current.getRenderShell === previous.installed.getRenderShell
|
|
464
|
-
? previous.downstream.getRenderShell
|
|
465
|
-
: current.getRenderShell,
|
|
466
|
-
getCallRenderer:
|
|
467
|
-
current.getCallRenderer === previous.installed.getCallRenderer
|
|
468
|
-
? previous.downstream.getCallRenderer
|
|
469
|
-
: current.getCallRenderer,
|
|
470
|
-
getResultRenderer:
|
|
471
|
-
current.getResultRenderer === previous.installed.getResultRenderer
|
|
472
|
-
? previous.downstream.getResultRenderer
|
|
473
|
-
: current.getResultRenderer,
|
|
474
|
-
};
|
|
451
|
+
if (!previous || previous.prototype !== prototype || !isOwnershipAwarePatch(previous)) {
|
|
452
|
+
return current;
|
|
475
453
|
}
|
|
476
|
-
|
|
477
|
-
// pre-v2 Symbol:originalX 显式记录真实下游(native 原型方法),无需源码嗅探。
|
|
478
|
-
// 缺失字段回退到当前原型方法(视作 external)。
|
|
479
|
-
const originalOrCurrent = (field: string, method: Function): Function =>
|
|
480
|
-
typeof previous[field] === "function" ? previous[field] : method;
|
|
481
454
|
return {
|
|
482
|
-
hasRendererDefinition:
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
getRenderShell:
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
getCallRenderer:
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
getResultRenderer:
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
455
|
+
hasRendererDefinition:
|
|
456
|
+
current.hasRendererDefinition === previous.installed.hasRendererDefinition
|
|
457
|
+
? previous.downstream.hasRendererDefinition
|
|
458
|
+
: current.hasRendererDefinition,
|
|
459
|
+
getRenderShell:
|
|
460
|
+
current.getRenderShell === previous.installed.getRenderShell
|
|
461
|
+
? previous.downstream.getRenderShell
|
|
462
|
+
: current.getRenderShell,
|
|
463
|
+
getCallRenderer:
|
|
464
|
+
current.getCallRenderer === previous.installed.getCallRenderer
|
|
465
|
+
? previous.downstream.getCallRenderer
|
|
466
|
+
: current.getCallRenderer,
|
|
467
|
+
getResultRenderer:
|
|
468
|
+
current.getResultRenderer === previous.installed.getResultRenderer
|
|
469
|
+
? previous.downstream.getResultRenderer
|
|
470
|
+
: current.getResultRenderer,
|
|
498
471
|
};
|
|
499
472
|
}
|
|
500
473
|
|
|
501
|
-
function disconnectGlobalToolRenderPatch(patch:
|
|
502
|
-
if (!patch
|
|
474
|
+
function disconnectGlobalToolRenderPatch(patch: GlobalToolRenderPatch | undefined): void {
|
|
475
|
+
if (!patch) return;
|
|
503
476
|
patch.active = false;
|
|
504
|
-
patch.enabled = () => false;
|
|
505
|
-
patch.mode = () => "off";
|
|
506
|
-
patch.wrap = (tool: any) => tool;
|
|
507
477
|
patch.byDefinition = new WeakMap();
|
|
508
|
-
|
|
509
|
-
else patch.byName = new Map();
|
|
478
|
+
patch.byName.clear();
|
|
510
479
|
}
|
|
511
480
|
|
|
512
481
|
function installGlobalToolRendering(
|
|
@@ -515,25 +484,18 @@ function installGlobalToolRendering(
|
|
|
515
484
|
const prototype = (ToolExecutionComponent as any).prototype;
|
|
516
485
|
const previous = patchRegistry.get<GlobalToolRenderPatch>(GLOBAL_TOOL_RENDER_PATCH);
|
|
517
486
|
const downstream = downstreamForGlobalToolInstall(prototype, previous);
|
|
518
|
-
|
|
519
|
-
disconnectGlobalToolRenderPatch(previous);
|
|
487
|
+
if (isOwnershipAwarePatch(previous)) disconnectGlobalToolRenderPatch(previous);
|
|
520
488
|
|
|
521
489
|
const patch: GlobalToolRenderPatch = {
|
|
522
490
|
version: 2,
|
|
523
491
|
prototype,
|
|
524
|
-
owner: {},
|
|
525
492
|
active: true,
|
|
526
|
-
enabled: () => config.mode === "on",
|
|
527
493
|
mode: () => config.mode,
|
|
528
494
|
wrap: (tool: any) => createCcstyleTool(tool, writeExecutionMetadata),
|
|
529
495
|
byDefinition: new WeakMap(),
|
|
530
496
|
byName: new Map(),
|
|
531
497
|
downstream,
|
|
532
498
|
installed: undefined as any,
|
|
533
|
-
originalHasRendererDefinition: downstream.hasRendererDefinition,
|
|
534
|
-
originalGetRenderShell: downstream.getRenderShell,
|
|
535
|
-
originalGetCallRenderer: downstream.getCallRenderer,
|
|
536
|
-
originalGetResultRenderer: downstream.getResultRenderer,
|
|
537
499
|
};
|
|
538
500
|
|
|
539
501
|
patch.installed = {
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
} from "./default-mode.ts";
|
|
15
15
|
import { isLazyProxyTui } from "../utils/fullscreen-detect.ts";
|
|
16
16
|
import { showCcstylePanel } from "../config/panel.ts";
|
|
17
|
-
import { GLOBAL_COMPACTION_RENDER_PATCH, patchRegistry } from "../utils/patch-keys.ts";
|
|
18
17
|
import {
|
|
19
18
|
config,
|
|
20
19
|
formatConfigStatus,
|
|
@@ -93,16 +92,6 @@ function applyStyleMode(mode: CompactStyleMode, ctx: any, toolGrouping?: ToolGro
|
|
|
93
92
|
ctx.ui.notify(`Claude Code style: ${mode}`, "info");
|
|
94
93
|
}
|
|
95
94
|
|
|
96
|
-
type LegacyCompactionRenderPatch = {
|
|
97
|
-
enabled?: () => boolean;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
/** Disable the pre-native compaction monkey patch left alive by /reload. */
|
|
101
|
-
function deactivateLegacyCompactionRendering() {
|
|
102
|
-
const patch = patchRegistry.get<LegacyCompactionRenderPatch>(GLOBAL_COMPACTION_RENDER_PATCH);
|
|
103
|
-
if (patch) patch.enabled = () => false;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
95
|
export default function (
|
|
107
96
|
pi: ExtensionAPI,
|
|
108
97
|
configOverride?: Partial<Config>,
|
|
@@ -137,7 +126,6 @@ export default function (
|
|
|
137
126
|
const disposeMessageDisplay = installMessageDisplayRendering();
|
|
138
127
|
// 展开背景必须在 compact-mode 之后装,shutdown 时先于 compact 释放。
|
|
139
128
|
const disposeToolExpandedBackground = installToolExpandedBackground();
|
|
140
|
-
deactivateLegacyCompactionRendering();
|
|
141
129
|
installation = {
|
|
142
130
|
defaultMode,
|
|
143
131
|
toolGrouping,
|
|
@@ -270,7 +258,6 @@ export default function (
|
|
|
270
258
|
current.compactMode.shutdown();
|
|
271
259
|
compactModeHooks = undefined;
|
|
272
260
|
current.disposeMessageDisplay();
|
|
273
|
-
deactivateLegacyCompactionRendering();
|
|
274
261
|
clearAllAnimations();
|
|
275
262
|
installation = undefined;
|
|
276
263
|
});
|
|
@@ -21,13 +21,8 @@ export function sharedToolHoverState(): SharedToolHoverState {
|
|
|
21
21
|
return patchRegistry.ensure(TOOL_HOVER_STATE_KEY, () => ({ toolCallId: null }));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
// 状态单源在 globalThis 槽;hoveredToolCallId 变量仅为兼容旧 deep import 保留
|
|
25
|
-
// (外部扩展可能直接从 interaction.ts re-export 读取),内部读取一律走槽。
|
|
26
|
-
export let hoveredToolCallId: string | null = sharedToolHoverState().toolCallId;
|
|
27
|
-
|
|
28
24
|
export function setHoveredToolCallId(toolCallId: string | null): void {
|
|
29
25
|
sharedToolHoverState().toolCallId = toolCallId;
|
|
30
|
-
hoveredToolCallId = toolCallId;
|
|
31
26
|
}
|
|
32
27
|
|
|
33
28
|
export function isToolCallHovered(toolCallId: string | null | undefined): boolean {
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
collectToolComponents,
|
|
20
20
|
extractToolFramePlacements,
|
|
21
21
|
isSgrLeftPress,
|
|
22
|
+
isSgrLeftRelease,
|
|
22
23
|
isToolExecutionComponent,
|
|
23
24
|
parseSgrMousePackets,
|
|
24
25
|
stripTerminalSequences,
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
isScrollbarColumnAt,
|
|
37
38
|
} from "./layout.ts";
|
|
38
39
|
import {
|
|
40
|
+
disableOfficialScrollToEnd,
|
|
39
41
|
fullscreenLazyTui,
|
|
40
42
|
hideScrollButton,
|
|
41
43
|
isScrollBottomInput,
|
|
@@ -156,7 +158,11 @@ function updateToolSummaryHover(tui: any, packet: SgrMousePacket): void {
|
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
const EXPAND_PANEL_DOUBLE_CLICK_MS = 400;
|
|
159
|
-
|
|
161
|
+
/** 松开后短时间内的重复按下视为同一次单击(终端会在 mouseup 后再打一次 press)。 */
|
|
162
|
+
const EXPAND_PANEL_CLICK_ARM_MS = 50;
|
|
163
|
+
let pendingExpandPress: { id: unknown } | null = null;
|
|
164
|
+
let lastExpandClick: { id: unknown; at: number } | null = null;
|
|
165
|
+
let armExpandClickTimer: ReturnType<typeof setTimeout> | null = null;
|
|
160
166
|
|
|
161
167
|
function expandPanelIdentity(card: any): unknown {
|
|
162
168
|
return card instanceof ThinkingPreviewBlock
|
|
@@ -167,17 +173,48 @@ function expandPanelIdentity(card: any): unknown {
|
|
|
167
173
|
function isExpandPanelDoubleClick(card: any): boolean {
|
|
168
174
|
const now = Date.now();
|
|
169
175
|
const id = expandPanelIdentity(card);
|
|
170
|
-
const prev =
|
|
171
|
-
|
|
172
|
-
|
|
176
|
+
const prev = lastExpandClick;
|
|
177
|
+
pendingExpandPress = { id };
|
|
178
|
+
if (prev && prev.id === id && now - prev.at <= EXPAND_PANEL_DOUBLE_CLICK_MS) {
|
|
179
|
+
pendingExpandPress = null;
|
|
180
|
+
lastExpandClick = null;
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function completeExpandPanelClick(): void {
|
|
187
|
+
if (!pendingExpandPress) return;
|
|
188
|
+
const click = { id: pendingExpandPress.id, at: Date.now() };
|
|
189
|
+
pendingExpandPress = null;
|
|
190
|
+
if (armExpandClickTimer) clearTimeout(armExpandClickTimer);
|
|
191
|
+
armExpandClickTimer = setTimeout(() => {
|
|
192
|
+
armExpandClickTimer = null;
|
|
193
|
+
// 50ms 内又按下:仍是同一次单击,不能武装成双击。
|
|
194
|
+
if (pendingExpandPress) return;
|
|
195
|
+
lastExpandClick = click;
|
|
196
|
+
}, EXPAND_PANEL_CLICK_ARM_MS);
|
|
197
|
+
if (
|
|
198
|
+
typeof armExpandClickTimer === "object" &&
|
|
199
|
+
armExpandClickTimer &&
|
|
200
|
+
"unref" in armExpandClickTimer
|
|
201
|
+
) {
|
|
202
|
+
armExpandClickTimer.unref();
|
|
203
|
+
}
|
|
173
204
|
}
|
|
174
205
|
|
|
175
206
|
function clearExpandPanelDoubleClick(): void {
|
|
176
|
-
|
|
207
|
+
pendingExpandPress = null;
|
|
208
|
+
lastExpandClick = null;
|
|
209
|
+
if (armExpandClickTimer) {
|
|
210
|
+
clearTimeout(armExpandClickTimer);
|
|
211
|
+
armExpandClickTimer = null;
|
|
212
|
+
}
|
|
177
213
|
}
|
|
178
214
|
|
|
179
215
|
function collapseExpandedCard(tui: any, card: any): boolean {
|
|
180
|
-
|
|
216
|
+
// 单击也 consume,避免官方链再合成一次 press 把单击当成双击。
|
|
217
|
+
if (!isExpandPanelDoubleClick(card)) return true;
|
|
181
218
|
card.setExpanded(false);
|
|
182
219
|
setHoveredToolCallId(null);
|
|
183
220
|
setHoveredToolGroup(null);
|
|
@@ -333,18 +370,17 @@ function handleFullscreenToolClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
333
370
|
const plain = stripTerminalSequencesPreservingLayout(line);
|
|
334
371
|
const section = view.matchShowMoreLine(plain);
|
|
335
372
|
if (section) {
|
|
373
|
+
clearExpandPanelDoubleClick();
|
|
336
374
|
const box = view.showMoreHitbox(plain);
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
});
|
|
347
|
-
}
|
|
375
|
+
return tryOpenToolIoShowMore({
|
|
376
|
+
kind: "show-more",
|
|
377
|
+
row: 0,
|
|
378
|
+
startCol: box?.startCol ?? 1,
|
|
379
|
+
endCol: box?.endCol ?? 1,
|
|
380
|
+
component,
|
|
381
|
+
view,
|
|
382
|
+
section,
|
|
383
|
+
});
|
|
348
384
|
}
|
|
349
385
|
}
|
|
350
386
|
// 双击收起:内部工具仍归所属 group。单击放行官方(选择/链接)。
|
|
@@ -461,6 +497,7 @@ function patchFullscreenViewportInput(tui: any): void {
|
|
|
461
497
|
if (packets && tui.hasOverlay?.() && hasActiveTextPreview()) return undefined;
|
|
462
498
|
if (packets && !tui.hasOverlay?.()) {
|
|
463
499
|
for (const packet of packets) {
|
|
500
|
+
if (isSgrLeftRelease(packet)) completeExpandPanelClick();
|
|
464
501
|
if (isSgrLeftPress(packet) && handleFullscreenToolClick(tui, packet)) {
|
|
465
502
|
return { consume: true };
|
|
466
503
|
}
|
|
@@ -797,6 +834,7 @@ function handleToolMouseInput(data: string): { consume: true } | undefined {
|
|
|
797
834
|
let consumed = false;
|
|
798
835
|
for (const packet of packets) {
|
|
799
836
|
updateToolSummaryHover(getToolMouseTui(), packet);
|
|
837
|
+
if (isSgrLeftRelease(packet)) completeExpandPanelClick();
|
|
800
838
|
if (!isSgrLeftPress(packet)) continue;
|
|
801
839
|
if (toggleToolAtMouseClick(getToolMouseTui(), packet)) {
|
|
802
840
|
consumed = true;
|
|
@@ -879,10 +917,12 @@ export function installToolMouseInteraction(
|
|
|
879
917
|
if (isLazyProxyTui(tui)) {
|
|
880
918
|
patchFullscreenViewportInput(tui);
|
|
881
919
|
ensureFullscreenToolMouseMotion(tui);
|
|
920
|
+
disableOfficialScrollToEnd(tui);
|
|
882
921
|
setScrollButtonWidget({
|
|
883
922
|
render: (width: number) => {
|
|
884
923
|
patchFullscreenViewportInput(tui);
|
|
885
924
|
ensureFullscreenToolMouseMotion(tui);
|
|
925
|
+
disableOfficialScrollToEnd(tui);
|
|
886
926
|
return renderScrollButton(width, theme);
|
|
887
927
|
},
|
|
888
928
|
invalidate() {},
|
|
@@ -924,13 +964,3 @@ export function scheduleSessionRender(refresh?: () => void): void {
|
|
|
924
964
|
tui.requestRender(true);
|
|
925
965
|
}, 0);
|
|
926
966
|
}
|
|
927
|
-
|
|
928
|
-
// toolMouseTui 跨模块读取一律走 getToolMouseTui()(jiti 转译下 let 绑定可能是快照);
|
|
929
|
-
// 此 re-export 仅保留兼容旧导入,新代码请用 getToolMouseTui。
|
|
930
|
-
export { toolMouseTui } from "./scroll.ts";
|
|
931
|
-
export {
|
|
932
|
-
hoveredToolCallId,
|
|
933
|
-
isToolCallHovered,
|
|
934
|
-
setHoveredToolGroup,
|
|
935
|
-
setHoveredToolIo,
|
|
936
|
-
} from "./hover.ts";
|
|
@@ -18,26 +18,14 @@ const ZENTUI_PAGE_UP_INPUT = /^\x1b\[5;9(?::[12])?~$|^\x1b\[57421;9(?::[12])?u$|
|
|
|
18
18
|
const ZENTUI_PAGE_DOWN_INPUT = /^\x1b\[6;9(?::[12])?~$|^\x1b\[57422;9(?::[12])?u$|^\x1b\[1;6B$/;
|
|
19
19
|
const SCROLL_BOTTOM_SHORTCUT = "ctrl+end";
|
|
20
20
|
|
|
21
|
-
/**
|
|
22
|
-
* 当前安装的 tui 宿主。宿主放本模块(滚动按钮/调度依赖它),
|
|
23
|
-
* 由 mouse-interaction 经 setToolMouseTui 维护;跨模块一律经绑定/setter 访问。
|
|
24
|
-
*
|
|
25
|
-
* 状态镜像到 globalThis(Symbol 槽):jiti 转译下经 re-export 链读取的
|
|
26
|
-
* 模块级 let 绑定是初始值快照(死绑定,实测恒 null),函数调用才是活引用。
|
|
27
|
-
* 跨模块读取一律用 getToolMouseTui(),避免拿到加载时的快照。
|
|
28
|
-
*/
|
|
29
21
|
patchRegistry.ensure(TOOL_MOUSE_TUI_SLOT, () => null);
|
|
30
|
-
export let toolMouseTui: any = null;
|
|
31
22
|
export function getToolMouseTui(): any {
|
|
32
23
|
return patchRegistry.get(TOOL_MOUSE_TUI_SLOT);
|
|
33
24
|
}
|
|
34
25
|
export function setToolMouseTui(tui: any): void {
|
|
35
|
-
toolMouseTui = tui;
|
|
36
26
|
patchRegistry.install(TOOL_MOUSE_TUI_SLOT, tui);
|
|
37
27
|
}
|
|
38
28
|
|
|
39
|
-
// 滚动按钮状态同 toolMouseTui:镜像到 globalThis(Symbol 槽),跨模块读取
|
|
40
|
-
// 一律用 getter(jiti 转译下模块级 let 绑定是初始值快照)。
|
|
41
29
|
type ScrollButtonState = { visible: boolean; hovered: boolean; widget: any };
|
|
42
30
|
function scrollButtonState(): ScrollButtonState {
|
|
43
31
|
return patchRegistry.ensure(SCROLL_BUTTON_STATE_SLOT, () => ({
|
|
@@ -91,6 +79,11 @@ export function fullscreenLazyTui(tui: any): boolean {
|
|
|
91
79
|
return isLazyProxyTui(tui) && tui.mode === "fullscreen";
|
|
92
80
|
}
|
|
93
81
|
|
|
82
|
+
/** 关掉 pi 0.85 Jump to latest overlay,避免和本仓库 dock 按钮叠两层。 */
|
|
83
|
+
export function disableOfficialScrollToEnd(tui: any): void {
|
|
84
|
+
if (typeof tui?.scrollToEndIndicator === "function") tui.scrollToEndIndicator = undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
94
87
|
/** 官方 fullscreen:是否已跟随 transcript 底部(按钮隐藏条件)。 */
|
|
95
88
|
export function isFullscreenAtBottom(tui: any): boolean {
|
|
96
89
|
const following = tui.isFollowingOutput ?? tui.getPrimaryScrollView?.()?.isFollowingEnd ?? true;
|
|
@@ -164,7 +157,7 @@ export function scheduleScrollButtonSync(tui: any, data: string): void {
|
|
|
164
157
|
const previousLines = tui.previousLines;
|
|
165
158
|
const check = (attempt: number) => {
|
|
166
159
|
scrollButtonSyncScheduled = false;
|
|
167
|
-
if (
|
|
160
|
+
if (getToolMouseTui() !== tui) return;
|
|
168
161
|
// Pi renders on its own frame timer. Inspect the resulting viewport before
|
|
169
162
|
// showing the button so empty or non-scrollable transcripts never flash it.
|
|
170
163
|
const rendered = tui.previousLines !== previousLines;
|
|
@@ -192,7 +185,7 @@ export function updateScrollButtonFromInput(tui: any, data: string): void {
|
|
|
192
185
|
}
|
|
193
186
|
|
|
194
187
|
export function renderScrollButton(width: number, theme: any): string[] {
|
|
195
|
-
if (!getScrollButtonVisible() || !fullscreenLazyTui(
|
|
188
|
+
if (!getScrollButtonVisible() || !fullscreenLazyTui(getToolMouseTui())) return [];
|
|
196
189
|
const shortcut = formatShortcut(SCROLL_BOTTOM_SHORTCUT);
|
|
197
190
|
const label = theme.fg(
|
|
198
191
|
getScrollButtonHovered() ? "text" : "accent",
|
|
@@ -45,18 +45,10 @@ export function resolveDiffDisplayLimit(
|
|
|
45
45
|
return expanded ? Math.max(0, expandedLimit) : Math.max(1, collapsedLimit);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
/** Write collapsed body limit. Missing/NaN falls back to edit's `editDiffCollapsedLines`. */
|
|
49
48
|
export function resolveWriteCollapsedLimit(
|
|
50
|
-
config: Pick<ToolDisplayConfig, "writeDiffCollapsedLines"
|
|
49
|
+
config: Pick<ToolDisplayConfig, "writeDiffCollapsedLines">,
|
|
51
50
|
): number {
|
|
52
|
-
|
|
53
|
-
if (Number.isFinite(value)) {
|
|
54
|
-
return Math.max(0, value);
|
|
55
|
-
}
|
|
56
|
-
const fallback = Number.isFinite(config.editDiffCollapsedLines)
|
|
57
|
-
? config.editDiffCollapsedLines
|
|
58
|
-
: DEFAULT_TOOL_DISPLAY_CONFIG.writeDiffCollapsedLines;
|
|
59
|
-
return Math.max(0, fallback);
|
|
51
|
+
return Math.max(0, config.writeDiffCollapsedLines);
|
|
60
52
|
}
|
|
61
53
|
|
|
62
54
|
/**
|
|
@@ -40,7 +40,6 @@ export interface DiffStats {
|
|
|
40
40
|
|
|
41
41
|
const CANONICAL_LINE_PATTERN = /^([+\- ])(\s*\d+)\|(.*)$/;
|
|
42
42
|
const HASHLINE_ANCHOR_LINE_PATTERN = /^([+\- ])(\s*\d+)#([A-Za-z0-9]+| {2}):(.*)$/;
|
|
43
|
-
const LEGACY_LINE_PATTERN = /^([+\- ])(\s*\d+)\s(.*)$/;
|
|
44
43
|
const HUNK_HEADER_PATTERN = /^@@\s+-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s+@@(.*)$/;
|
|
45
44
|
const MIN_LINE_NUMBER_WIDTH = 2;
|
|
46
45
|
|
|
@@ -81,14 +80,8 @@ function parseCanonicalDiffLine(line: string): {
|
|
|
81
80
|
};
|
|
82
81
|
}
|
|
83
82
|
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
const matched = canonicalMatch ?? legacyMatch;
|
|
87
|
-
if (!matched) {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return toParsedDiffLine(matched[1] ?? " ", matched[2] ?? "", matched[3] ?? "");
|
|
83
|
+
const match = line.match(CANONICAL_LINE_PATTERN);
|
|
84
|
+
return match ? toParsedDiffLine(match[1] ?? " ", match[2] ?? "", match[3] ?? "") : null;
|
|
92
85
|
}
|
|
93
86
|
|
|
94
87
|
function toNumber(value: string | undefined): number | null {
|
|
@@ -204,7 +204,7 @@ export class ExpandedToolResultText {
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
/**
|
|
207
|
+
/** 截断体末行 `… +N more lines` 旁的展开提示,点击打开全量预览。 */
|
|
208
208
|
export const SHOW_MORE_LABEL = "• click to show more";
|
|
209
209
|
|
|
210
210
|
export type ToolIoSection = "input" | "output";
|
|
@@ -251,8 +251,8 @@ export class ExpandedToolIoView {
|
|
|
251
251
|
inputBody: string,
|
|
252
252
|
outputBody: string,
|
|
253
253
|
isError: boolean,
|
|
254
|
-
maxOutputLines = config.
|
|
255
|
-
maxInputLines = config.
|
|
254
|
+
maxOutputLines = config.expandedOutputMaxLines,
|
|
255
|
+
maxInputLines = config.expandedInputMaxLines,
|
|
256
256
|
flushLeft = false,
|
|
257
257
|
) {
|
|
258
258
|
this.theme = theme;
|
|
@@ -309,12 +309,12 @@ export class ExpandedToolIoView {
|
|
|
309
309
|
this.invalidate();
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
-
/** True when the plain
|
|
312
|
+
/** True when the plain truncation footer carries show-more. Input 续行带 │,Output 不带。 */
|
|
313
313
|
matchShowMoreLine(plainLine: string): ToolIoSection | null {
|
|
314
314
|
const line = plainLine.replace(/\x1b\[[0-9;]*m/g, "");
|
|
315
|
-
if (!line.includes(` • ${showMoreHintText()}`)) return null;
|
|
316
|
-
if (
|
|
317
|
-
if (
|
|
315
|
+
if (!line.includes(` • ${showMoreHintText()}`) || !/\+\d+ more lines/.test(line)) return null;
|
|
316
|
+
if (this.truncated.input && line.includes("│")) return "input";
|
|
317
|
+
if (this.truncated.output) return "output";
|
|
318
318
|
return null;
|
|
319
319
|
}
|
|
320
320
|
|
|
@@ -360,24 +360,13 @@ export class ExpandedToolIoView {
|
|
|
360
360
|
this.truncated = { input: false, output: false };
|
|
361
361
|
this.showMoreHeaderRows = {};
|
|
362
362
|
|
|
363
|
-
const pushHeader = (
|
|
364
|
-
corner: "├" | "└",
|
|
365
|
-
label: string,
|
|
366
|
-
section: ToolIoSection,
|
|
367
|
-
showMore: boolean,
|
|
368
|
-
) => {
|
|
363
|
+
const pushHeader = (corner: "├" | "└", label: string) => {
|
|
369
364
|
const mark = theme.fg("dim", `${lead}${corner} `);
|
|
370
365
|
const title = theme.fg(
|
|
371
366
|
"accent",
|
|
372
367
|
typeof theme.bold === "function" ? theme.bold(label) : label,
|
|
373
368
|
);
|
|
374
|
-
|
|
375
|
-
const more = showMore
|
|
376
|
-
? theme.fg("dim", " •") +
|
|
377
|
-
theme.fg(this.hoveredSection === section ? "text" : "dim", ` ${showMoreHintText()}`)
|
|
378
|
-
: "";
|
|
379
|
-
if (showMore) this.showMoreHeaderRows[section] = lines.length;
|
|
380
|
-
lines.push(truncateToWidth(mark + title + more, safeWidth, ""));
|
|
369
|
+
lines.push(truncateToWidth(mark + title, safeWidth, ""));
|
|
381
370
|
};
|
|
382
371
|
|
|
383
372
|
const pushRailLine = (styledContent: string, continued = true) => {
|
|
@@ -400,7 +389,7 @@ export class ExpandedToolIoView {
|
|
|
400
389
|
|
|
401
390
|
const pushBody = (
|
|
402
391
|
body: string,
|
|
403
|
-
opts: { input?: boolean; limit: number; continued?: boolean },
|
|
392
|
+
opts: { input?: boolean; limit: number; continued?: boolean; section: ToolIoSection },
|
|
404
393
|
): boolean /* truncated */ => {
|
|
405
394
|
const raw = body.replace(/\t/g, " ").replace(/\n+$/, "");
|
|
406
395
|
if (!raw.trim()) {
|
|
@@ -423,7 +412,15 @@ export class ExpandedToolIoView {
|
|
|
423
412
|
if (truncated) {
|
|
424
413
|
const hidden = Math.max(0, wrapped.length - visible.length);
|
|
425
414
|
if (hidden > 0) {
|
|
426
|
-
|
|
415
|
+
// hover 只高亮文字,圆点保持 dim(与 group hint 一致)。
|
|
416
|
+
const more =
|
|
417
|
+
theme.fg("dim", " •") +
|
|
418
|
+
theme.fg(
|
|
419
|
+
this.hoveredSection === opts.section ? "text" : "dim",
|
|
420
|
+
` ${showMoreHintText()}`,
|
|
421
|
+
);
|
|
422
|
+
pushRailLine(theme.fg("dim", `… +${hidden} more lines`) + more, opts.continued);
|
|
423
|
+
this.showMoreHeaderRows[opts.section] = lines.length - 1;
|
|
427
424
|
}
|
|
428
425
|
}
|
|
429
426
|
return truncated;
|
|
@@ -447,20 +444,29 @@ export class ExpandedToolIoView {
|
|
|
447
444
|
|
|
448
445
|
if (hasInput) {
|
|
449
446
|
this.truncated.input = inputWouldTruncate;
|
|
450
|
-
pushHeader("├", "Input"
|
|
447
|
+
pushHeader("├", "Input");
|
|
451
448
|
pushBody(this.inputBody, {
|
|
452
449
|
input: true,
|
|
453
450
|
limit: this.maxInputLines,
|
|
454
451
|
continued: true,
|
|
452
|
+
section: "input",
|
|
455
453
|
});
|
|
456
454
|
pushBlankRail();
|
|
457
455
|
this.truncated.output = outputWouldTruncate;
|
|
458
|
-
pushHeader("└", "Output"
|
|
459
|
-
pushBody(outputText, {
|
|
456
|
+
pushHeader("└", "Output");
|
|
457
|
+
pushBody(outputText, {
|
|
458
|
+
limit: this.maxOutputLines,
|
|
459
|
+
continued: false,
|
|
460
|
+
section: "output",
|
|
461
|
+
});
|
|
460
462
|
} else {
|
|
461
463
|
this.truncated.output = outputWouldTruncate;
|
|
462
|
-
pushHeader("└", "Output"
|
|
463
|
-
pushBody(outputText, {
|
|
464
|
+
pushHeader("└", "Output");
|
|
465
|
+
pushBody(outputText, {
|
|
466
|
+
limit: this.maxOutputLines,
|
|
467
|
+
continued: false,
|
|
468
|
+
section: "output",
|
|
469
|
+
});
|
|
464
470
|
}
|
|
465
471
|
|
|
466
472
|
this.cachedWidth = width;
|
|
@@ -663,13 +669,21 @@ export function renderExpandedToolResult(
|
|
|
663
669
|
): ExpandedToolIoView | ExpandedToolResultText | Text {
|
|
664
670
|
const inputBody = formatToolInputArgs(args);
|
|
665
671
|
const outputBody = body;
|
|
666
|
-
const
|
|
672
|
+
const maxOutputLines = config.expandedOutputMaxLines;
|
|
673
|
+
const maxInputLines = config.expandedInputMaxLines;
|
|
667
674
|
|
|
668
675
|
// Prefer structured Input/Output when we have args or non-empty output.
|
|
669
676
|
if (inputBody.trim() || outputBody.trim()) {
|
|
670
677
|
let view: ExpandedToolIoView;
|
|
671
678
|
if (isExpandedToolIoView(lastComponent)) {
|
|
672
|
-
lastComponent.setContent(
|
|
679
|
+
lastComponent.setContent(
|
|
680
|
+
inputBody,
|
|
681
|
+
outputBody,
|
|
682
|
+
isError,
|
|
683
|
+
maxOutputLines,
|
|
684
|
+
maxInputLines,
|
|
685
|
+
flushLeft,
|
|
686
|
+
);
|
|
673
687
|
view = lastComponent;
|
|
674
688
|
} else {
|
|
675
689
|
view = new ExpandedToolIoView(
|
|
@@ -677,8 +691,8 @@ export function renderExpandedToolResult(
|
|
|
677
691
|
inputBody,
|
|
678
692
|
outputBody,
|
|
679
693
|
isError,
|
|
680
|
-
|
|
681
|
-
|
|
694
|
+
maxOutputLines,
|
|
695
|
+
maxInputLines,
|
|
682
696
|
flushLeft,
|
|
683
697
|
);
|
|
684
698
|
}
|