pi-cc-extensions 0.8.22 → 0.8.24
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/extensions/claude-code-style.ts +293 -66
- package/package.json +1 -1
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
3
|
-
getSettingsListTheme,
|
|
4
|
-
keyHint,
|
|
5
|
-
ToolExecutionComponent,
|
|
6
|
-
} from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { getSettingsListTheme, ToolExecutionComponent } from "@earendil-works/pi-coding-agent";
|
|
7
3
|
import {
|
|
8
4
|
installCompactStyle,
|
|
9
5
|
type CompactStyleHooks,
|
|
@@ -228,6 +224,12 @@ function saveConfig() {
|
|
|
228
224
|
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
229
225
|
}
|
|
230
226
|
|
|
227
|
+
const TOOL_VIEWPORT_WIDTH_RATIO = 0.8;
|
|
228
|
+
|
|
229
|
+
function toolViewportWidth(width: number): number {
|
|
230
|
+
return Math.max(1, Math.floor(width * TOOL_VIEWPORT_WIDTH_RATIO));
|
|
231
|
+
}
|
|
232
|
+
|
|
231
233
|
function oneLine(value: unknown, max = 72): string {
|
|
232
234
|
const text = String(value ?? "")
|
|
233
235
|
.replace(/\s+/g, " ")
|
|
@@ -235,10 +237,19 @@ function oneLine(value: unknown, max = 72): string {
|
|
|
235
237
|
return text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
236
238
|
}
|
|
237
239
|
|
|
238
|
-
function
|
|
240
|
+
function rawTextFromResult(result: any): string {
|
|
239
241
|
const item = result?.content?.find?.((c: any) => c.type === "text");
|
|
240
|
-
|
|
241
|
-
|
|
242
|
+
return item?.type === "text" ? String(item.text ?? "") : "";
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function textFromResult(result: any): string {
|
|
246
|
+
// Compact previews only need short text; bound sanitize work.
|
|
247
|
+
return sanitizeToolResultText(rawTextFromResult(result), 16_384);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function outputLineCount(result: any): number {
|
|
251
|
+
const text = rawTextFromResult(result).replace(/\r\n?/g, "\n").replace(/\n+$/, "");
|
|
252
|
+
return text ? text.split("\n").length : 0;
|
|
242
253
|
}
|
|
243
254
|
|
|
244
255
|
function countLines(text: string): number {
|
|
@@ -415,6 +426,10 @@ export const SHOW_MORE_LABEL = "[show more]";
|
|
|
415
426
|
|
|
416
427
|
export type ToolIoSection = "input" | "output";
|
|
417
428
|
|
|
429
|
+
// Module-local token: /reload creates a fresh token, so stale view instances are
|
|
430
|
+
// replaced instead of retaining their old render implementation.
|
|
431
|
+
const EXPANDED_TOOL_IO_VIEW_GENERATION = Symbol("ccstyle-expanded-tool-io-view");
|
|
432
|
+
|
|
418
433
|
/**
|
|
419
434
|
* Expanded tool body with clear Input / Output sections (Grok Build–style).
|
|
420
435
|
*
|
|
@@ -428,6 +443,7 @@ export type ToolIoSection = "input" | "output";
|
|
|
428
443
|
* Reused across re-renders via context.lastComponent when possible.
|
|
429
444
|
*/
|
|
430
445
|
export class ExpandedToolIoView {
|
|
446
|
+
readonly [EXPANDED_TOOL_IO_VIEW_GENERATION] = true;
|
|
431
447
|
private inputBody: string;
|
|
432
448
|
private outputBody: string;
|
|
433
449
|
private isError: boolean;
|
|
@@ -436,6 +452,7 @@ export class ExpandedToolIoView {
|
|
|
436
452
|
private maxInputLines: number;
|
|
437
453
|
private cachedWidth: number | undefined;
|
|
438
454
|
private cachedLines: string[] | undefined;
|
|
455
|
+
private hoveredSection: ToolIoSection | null = null;
|
|
439
456
|
/** Which sections currently show the [show more] affordance (after last render). */
|
|
440
457
|
private truncated: { input: boolean; output: boolean } = { input: false, output: false };
|
|
441
458
|
|
|
@@ -490,6 +507,12 @@ export class ExpandedToolIoView {
|
|
|
490
507
|
return this.outputBody.trim() ? this.outputBody : "Done";
|
|
491
508
|
}
|
|
492
509
|
|
|
510
|
+
setHoveredSection(section: ToolIoSection | null): void {
|
|
511
|
+
if (this.hoveredSection === section) return;
|
|
512
|
+
this.hoveredSection = section;
|
|
513
|
+
this.invalidate();
|
|
514
|
+
}
|
|
515
|
+
|
|
493
516
|
/** True when the plain header line is a truncated section with [show more]. */
|
|
494
517
|
matchShowMoreLine(plainLine: string): ToolIoSection | null {
|
|
495
518
|
const line = plainLine.replace(/\x1b\[[0-9;]*m/g, "");
|
|
@@ -517,18 +540,26 @@ export class ExpandedToolIoView {
|
|
|
517
540
|
const safeWidth = Math.max(1, Math.floor(width));
|
|
518
541
|
const rail = " │ ";
|
|
519
542
|
const railWidth = visibleWidth(rail);
|
|
520
|
-
const
|
|
543
|
+
const bodyWidth = toolViewportWidth(safeWidth);
|
|
544
|
+
const contentWidth = Math.max(1, bodyWidth - railWidth);
|
|
521
545
|
const bodyColor = this.isError ? "error" : "toolOutput";
|
|
522
546
|
const lines: string[] = [];
|
|
523
547
|
this.truncated = { input: false, output: false };
|
|
524
548
|
|
|
525
|
-
const pushHeader = (
|
|
549
|
+
const pushHeader = (
|
|
550
|
+
corner: "┌" | "└",
|
|
551
|
+
label: string,
|
|
552
|
+
section: ToolIoSection,
|
|
553
|
+
showMore: boolean,
|
|
554
|
+
) => {
|
|
526
555
|
const mark = theme.fg("dim", ` ${corner} `);
|
|
527
556
|
const title = theme.fg(
|
|
528
557
|
"accent",
|
|
529
558
|
typeof theme.bold === "function" ? theme.bold(label) : label,
|
|
530
559
|
);
|
|
531
|
-
const more = showMore
|
|
560
|
+
const more = showMore
|
|
561
|
+
? theme.fg(this.hoveredSection === section ? "text" : "dim", ` ${SHOW_MORE_LABEL}`)
|
|
562
|
+
: "";
|
|
532
563
|
lines.push(truncateToWidth(mark + title + more, safeWidth, ""));
|
|
533
564
|
};
|
|
534
565
|
|
|
@@ -597,15 +628,15 @@ export class ExpandedToolIoView {
|
|
|
597
628
|
|
|
598
629
|
if (hasInput) {
|
|
599
630
|
this.truncated.input = inputWouldTruncate;
|
|
600
|
-
pushHeader("┌", "Input", inputWouldTruncate);
|
|
631
|
+
pushHeader("┌", "Input", "input", inputWouldTruncate);
|
|
601
632
|
pushBody(this.inputBody, { input: true, limit: this.maxInputLines });
|
|
602
633
|
pushBlankRail();
|
|
603
634
|
this.truncated.output = outputWouldTruncate;
|
|
604
|
-
pushHeader("└", "Output", outputWouldTruncate);
|
|
635
|
+
pushHeader("└", "Output", "output", outputWouldTruncate);
|
|
605
636
|
pushBody(outputText, { limit: this.maxOutputLines });
|
|
606
637
|
} else {
|
|
607
638
|
this.truncated.output = outputWouldTruncate;
|
|
608
|
-
pushHeader("┌", "Output", outputWouldTruncate);
|
|
639
|
+
pushHeader("┌", "Output", "output", outputWouldTruncate);
|
|
609
640
|
pushBody(outputText, { limit: this.maxOutputLines });
|
|
610
641
|
}
|
|
611
642
|
|
|
@@ -620,6 +651,18 @@ export class ExpandedToolIoView {
|
|
|
620
651
|
}
|
|
621
652
|
}
|
|
622
653
|
|
|
654
|
+
function isExpandedToolIoView(value: unknown): value is ExpandedToolIoView {
|
|
655
|
+
return Boolean(
|
|
656
|
+
value &&
|
|
657
|
+
typeof value === "object" &&
|
|
658
|
+
(value as ExpandedToolIoView)[EXPANDED_TOOL_IO_VIEW_GENERATION] === true &&
|
|
659
|
+
typeof (value as ExpandedToolIoView).getInputBody === "function" &&
|
|
660
|
+
typeof (value as ExpandedToolIoView).getOutputBody === "function" &&
|
|
661
|
+
typeof (value as ExpandedToolIoView).setHoveredSection === "function" &&
|
|
662
|
+
typeof (value as ExpandedToolIoView).render === "function",
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
|
|
623
666
|
/** True when body needs truncation at the given line limit (source lines or wrapped rows). */
|
|
624
667
|
function bodyExceedsLineLimit(
|
|
625
668
|
body: string,
|
|
@@ -655,6 +698,21 @@ export function renderCollapsedToolResult(body: string, collapsedHint = ""): str
|
|
|
655
698
|
return ` ↳ ${body}${collapsedHint}`;
|
|
656
699
|
}
|
|
657
700
|
|
|
701
|
+
export function renderCollapsedToolResultToWidth(
|
|
702
|
+
body: string,
|
|
703
|
+
collapsedHint: string,
|
|
704
|
+
width: number,
|
|
705
|
+
): string {
|
|
706
|
+
const previewWidth = toolViewportWidth(width);
|
|
707
|
+
const prefix = " ↳ ";
|
|
708
|
+
const bodyWidth = Math.max(1, previewWidth - visibleWidth(prefix) - visibleWidth(collapsedHint));
|
|
709
|
+
return truncateToWidth(
|
|
710
|
+
prefix + middleTruncateToWidth(body, bodyWidth) + collapsedHint,
|
|
711
|
+
previewWidth,
|
|
712
|
+
"",
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
|
|
658
716
|
/** Pretty-print tool call args for the expanded Input section. */
|
|
659
717
|
export function formatToolInputArgs(args: unknown, maxChars = 8_000): string {
|
|
660
718
|
if (args === undefined || args === null) return "";
|
|
@@ -752,7 +810,7 @@ function renderExpandedToolResult(
|
|
|
752
810
|
// Prefer structured Input/Output when we have args or non-empty output.
|
|
753
811
|
if (inputBody.trim() || outputBody.trim()) {
|
|
754
812
|
let view: ExpandedToolIoView;
|
|
755
|
-
if (lastComponent
|
|
813
|
+
if (isExpandedToolIoView(lastComponent)) {
|
|
756
814
|
lastComponent.setContent(inputBody, outputBody, isError, maxLines, maxLines);
|
|
757
815
|
view = lastComponent;
|
|
758
816
|
} else {
|
|
@@ -767,10 +825,14 @@ function renderExpandedToolResult(
|
|
|
767
825
|
return new Text(theme.fg(color, renderCollapsedToolResult("Done")), 0, 0);
|
|
768
826
|
}
|
|
769
827
|
|
|
770
|
-
function
|
|
828
|
+
export function formatExpandHint(lineCount: number): string {
|
|
829
|
+
return ` (${lineCount} more line${lineCount === 1 ? "" : "s"} / click)`;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function expandHint(theme: any, lineCount: number, hovered = false): string {
|
|
771
833
|
// Keep interaction guidance neutral; it should not inherit success/error
|
|
772
834
|
// coloring from the tool result surrounding it.
|
|
773
|
-
return
|
|
835
|
+
return theme.fg(hovered ? "text" : "muted", formatExpandHint(lineCount));
|
|
774
836
|
}
|
|
775
837
|
|
|
776
838
|
type SgrMousePacket = {
|
|
@@ -784,10 +846,12 @@ type ToolRenderHit = {
|
|
|
784
846
|
component: any;
|
|
785
847
|
start: number;
|
|
786
848
|
end: number;
|
|
849
|
+
row: number;
|
|
787
850
|
};
|
|
788
851
|
|
|
789
852
|
const TOOL_MOUSE_WIDGET_KEY = "ccstyle-tool-mouse";
|
|
790
853
|
const TOOL_MOUSE_ENABLE = "\x1b[?1000h\x1b[?1003h\x1b[?1006h";
|
|
854
|
+
const TOOL_MOUSE_MOTION_ENABLE = "\x1b[?1003h\x1b[?1006h";
|
|
791
855
|
const TOOL_MOUSE_DISABLE = "\x1b[?1006l\x1b[?1003l\x1b[?1000l";
|
|
792
856
|
const ZENTUI_PAGE_UP_INPUT = /^\x1b\[5;9(?::[12])?~$|^\x1b\[57421;9(?::[12])?u$|^\x1b\[1;6A$/;
|
|
793
857
|
const ZENTUI_PAGE_DOWN_INPUT = /^\x1b\[6;9(?::[12])?~$|^\x1b\[57422;9(?::[12])?u$|^\x1b\[1;6B$/;
|
|
@@ -804,6 +868,10 @@ let toolMouseInputUnsubscribe: (() => void) | null = null;
|
|
|
804
868
|
let toolMouseInputPatchTui: any = null;
|
|
805
869
|
let toolMouseInputPatchOriginalHandle: ((...args: any[]) => any) | null = null;
|
|
806
870
|
let toolMouseInputPatchWrapper: ((...args: any[]) => any) | null = null;
|
|
871
|
+
let toolMouseRenderPatchTui: any = null;
|
|
872
|
+
let toolMouseRenderPatchOriginal: ((...args: any[]) => any) | null = null;
|
|
873
|
+
let toolMouseRenderPatchWrapper: ((...args: any[]) => any) | null = null;
|
|
874
|
+
let toolMouseRawWrite: ((data: string) => unknown) | null = null;
|
|
807
875
|
let scrollButtonVisible = false;
|
|
808
876
|
let scrollButtonWidget: any = null;
|
|
809
877
|
let pendingScrollMessages = 0;
|
|
@@ -836,12 +904,14 @@ function isSgrLeftPress(packet: SgrMousePacket): boolean {
|
|
|
836
904
|
return packet.final === "M" && baseButton === 0 && (packet.code & 32) === 0;
|
|
837
905
|
}
|
|
838
906
|
|
|
839
|
-
function
|
|
907
|
+
function stripTerminalSequencesPreservingLayout(value: string): string {
|
|
840
908
|
return value
|
|
841
909
|
.replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "")
|
|
842
|
-
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
|
|
843
|
-
|
|
844
|
-
|
|
910
|
+
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function stripTerminalSequences(value: string): string {
|
|
914
|
+
return stripTerminalSequencesPreservingLayout(value).replace(/\s+/g, " ").trim();
|
|
845
915
|
}
|
|
846
916
|
|
|
847
917
|
function isToolExecutionComponent(value: any): boolean {
|
|
@@ -883,7 +953,7 @@ function collectToolRenderHits(
|
|
|
883
953
|
|
|
884
954
|
const count = renderedLineCount(component, width, cache);
|
|
885
955
|
if (isToolExecutionComponent(component)) {
|
|
886
|
-
if (count > 0) hits.push({ component, start, end: start + count });
|
|
956
|
+
if (count > 0) hits.push({ component, start, end: start + count, row: 0 });
|
|
887
957
|
return start + count;
|
|
888
958
|
}
|
|
889
959
|
|
|
@@ -946,8 +1016,10 @@ function findToolAtFixedEditorRow(
|
|
|
946
1016
|
): ToolRenderHit | null {
|
|
947
1017
|
if (visibleRow < 0 || visibleRow >= previousLines.length) return null;
|
|
948
1018
|
const visibleLines = previousLines.map((line) => stripTerminalSequences(String(line)));
|
|
1019
|
+
const rawClickedLine = String(previousLines[visibleRow] ?? "");
|
|
949
1020
|
const clickedLine = visibleLines[visibleRow] ?? "";
|
|
950
|
-
|
|
1021
|
+
const hasBackground = /\x1b\[(?:4[0-8]|10[0-7])(?:;[0-9;:]*)?m/.test(rawClickedLine);
|
|
1022
|
+
if (!clickedLine && !hasBackground) return null;
|
|
951
1023
|
|
|
952
1024
|
const tools: any[] = [];
|
|
953
1025
|
collectToolComponents(tui, tools);
|
|
@@ -964,7 +1036,12 @@ function findToolAtFixedEditorRow(
|
|
|
964
1036
|
const score = fixedEditorContextScore(renderedLines, renderedRow, visibleLines, visibleRow);
|
|
965
1037
|
if (!best || score > best.score) {
|
|
966
1038
|
best = {
|
|
967
|
-
hit: {
|
|
1039
|
+
hit: {
|
|
1040
|
+
component,
|
|
1041
|
+
start: visibleRow,
|
|
1042
|
+
end: visibleRow + renderedLines.length,
|
|
1043
|
+
row: renderedRow,
|
|
1044
|
+
},
|
|
968
1045
|
score,
|
|
969
1046
|
};
|
|
970
1047
|
if (score >= TOOL_CLICK_EARLY_EXIT_SCORE) return best.hit;
|
|
@@ -997,7 +1074,7 @@ function findToolAtScreenRow(tui: any, screenRow: number): ToolRenderHit | null
|
|
|
997
1074
|
// The stable summary marker keeps the whole row clickable even when the
|
|
998
1075
|
// trailing keyboard/click hint was truncated (common for subagent output).
|
|
999
1076
|
if (!hit.component.expanded && !COLLAPSED_TOOL_SUMMARY.test(clickedLine)) continue;
|
|
1000
|
-
return hit;
|
|
1077
|
+
return { ...hit, row: bufferRow - hit.start };
|
|
1001
1078
|
}
|
|
1002
1079
|
return null;
|
|
1003
1080
|
}
|
|
@@ -1364,6 +1441,8 @@ function scheduleCollapseViewportCompensation(
|
|
|
1364
1441
|
|
|
1365
1442
|
/** toolCallId → latest expanded IO view (survives context/state identity quirks). */
|
|
1366
1443
|
const ioViewsByToolCallId = new Map<string, ExpandedToolIoView>();
|
|
1444
|
+
let hoveredToolIoView: ExpandedToolIoView | null = null;
|
|
1445
|
+
let hoveredToolIoSection: ToolIoSection | null = null;
|
|
1367
1446
|
|
|
1368
1447
|
function rememberIoView(context: any, view: ExpandedToolIoView): void {
|
|
1369
1448
|
if (!context || typeof context !== "object") return;
|
|
@@ -1387,7 +1466,7 @@ function rememberIoView(context: any, view: ExpandedToolIoView): void {
|
|
|
1387
1466
|
|
|
1388
1467
|
function resolveIoViewFromTool(component: any): ExpandedToolIoView | null {
|
|
1389
1468
|
const fromState = component?.state?.ccstyleIoView;
|
|
1390
|
-
if (fromState
|
|
1469
|
+
if (isExpandedToolIoView(fromState)) return fromState;
|
|
1391
1470
|
const id =
|
|
1392
1471
|
(typeof component?.toolCallId === "string" && component.toolCallId) ||
|
|
1393
1472
|
(typeof component?.state?.toolCallId === "string" && component.state.toolCallId) ||
|
|
@@ -1398,7 +1477,7 @@ function resolveIoViewFromTool(component: any): ExpandedToolIoView | null {
|
|
|
1398
1477
|
}
|
|
1399
1478
|
// Some hosts keep the last result component on the tool instance.
|
|
1400
1479
|
const last = component?.lastComponent ?? component?.resultComponent ?? component?.content;
|
|
1401
|
-
if (last
|
|
1480
|
+
if (isExpandedToolIoView(last)) return last;
|
|
1402
1481
|
return null;
|
|
1403
1482
|
}
|
|
1404
1483
|
|
|
@@ -1406,6 +1485,25 @@ function resolveIoViewFromTool(component: any): ExpandedToolIoView | null {
|
|
|
1406
1485
|
* If the click lands on a truncated section's [show more], open the /context-style
|
|
1407
1486
|
* full-text preview instead of toggling expand/collapse.
|
|
1408
1487
|
*/
|
|
1488
|
+
function visibleMouseLine(tui: any, packet: SgrMousePacket): string {
|
|
1489
|
+
const previousLines = Array.isArray(tui?.previousLines) ? tui.previousLines : [];
|
|
1490
|
+
const viewportTop = useFixedEditorFeatures(tui)
|
|
1491
|
+
? 0
|
|
1492
|
+
: Number.isFinite(tui?.previousViewportTop)
|
|
1493
|
+
? tui.previousViewportTop
|
|
1494
|
+
: 0;
|
|
1495
|
+
return stripTerminalSequencesPreservingLayout(
|
|
1496
|
+
String(previousLines[viewportTop + packet.row - 1] ?? ""),
|
|
1497
|
+
);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
function isCollapsedHintAtColumn(line: string, col: number): boolean {
|
|
1501
|
+
const match = /(\([^()\n]* \/ click\))\s*$/.exec(line);
|
|
1502
|
+
if (!match?.[1] || col <= 0) return false;
|
|
1503
|
+
const start = visibleWidth(line.slice(0, match.index)) + 1;
|
|
1504
|
+
return col >= start && col < start + visibleWidth(match[1]);
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1409
1507
|
function tryOpenToolIoShowMore(tui: any, packet: SgrMousePacket, hit: ToolRenderHit): boolean {
|
|
1410
1508
|
if (!Boolean(hit.component.expanded)) return false;
|
|
1411
1509
|
const ioView = resolveIoViewFromTool(hit.component);
|
|
@@ -1417,13 +1515,12 @@ function tryOpenToolIoShowMore(tui: any, packet: SgrMousePacket, hit: ToolRender
|
|
|
1417
1515
|
: (Number.isFinite(tui?.previousViewportTop) ? tui.previousViewportTop : 0) + packet.row - 1;
|
|
1418
1516
|
if (visibleRow < 0 || visibleRow >= previousLines.length) return false;
|
|
1419
1517
|
|
|
1420
|
-
const plain =
|
|
1518
|
+
const plain = stripTerminalSequencesPreservingLayout(String(previousLines[visibleRow] ?? ""));
|
|
1421
1519
|
const section = ioView.matchShowMoreLine(plain);
|
|
1422
1520
|
if (!section) return false;
|
|
1423
1521
|
|
|
1424
|
-
// Prefer the [show more] cells; allow a little slack so imprecise clicks still work.
|
|
1425
1522
|
const box = ioView.showMoreHitbox(plain);
|
|
1426
|
-
if (box
|
|
1523
|
+
if (!box || packet.col < box.startCol || packet.col > box.endCol) return false;
|
|
1427
1524
|
|
|
1428
1525
|
const ui = toolMouseUi;
|
|
1429
1526
|
if (!ui || typeof ui.custom !== "function") {
|
|
@@ -1438,31 +1535,85 @@ function tryOpenToolIoShowMore(tui: any, packet: SgrMousePacket, hit: ToolRender
|
|
|
1438
1535
|
return true;
|
|
1439
1536
|
}
|
|
1440
1537
|
|
|
1538
|
+
function setHoveredToolIo(view: ExpandedToolIoView | null, section: ToolIoSection | null): boolean {
|
|
1539
|
+
if (view === hoveredToolIoView && section === hoveredToolIoSection) return false;
|
|
1540
|
+
hoveredToolIoView?.setHoveredSection(null);
|
|
1541
|
+
hoveredToolIoView = view;
|
|
1542
|
+
hoveredToolIoSection = section;
|
|
1543
|
+
view?.setHoveredSection(section);
|
|
1544
|
+
return true;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1441
1547
|
function updateToolSummaryHover(tui: any, packet: SgrMousePacket): void {
|
|
1442
1548
|
if ((packet.code & 32) === 0 || packet.final !== "M") return;
|
|
1443
|
-
const
|
|
1444
|
-
|
|
1445
|
-
|
|
1549
|
+
const visibleLine = visibleMouseLine(tui, packet);
|
|
1550
|
+
let nextToolCallId: string | null = null;
|
|
1551
|
+
let nextIoView: ExpandedToolIoView | null = null;
|
|
1552
|
+
let nextIoSection: ToolIoSection | null = null;
|
|
1553
|
+
|
|
1554
|
+
if (
|
|
1555
|
+
COLLAPSED_TOOL_SUMMARY.test(visibleLine) &&
|
|
1556
|
+
isCollapsedHintAtColumn(visibleLine, packet.col)
|
|
1557
|
+
) {
|
|
1558
|
+
const hit = findToolAtScreenRow(tui, packet.row);
|
|
1559
|
+
nextToolCallId = hit && !hit.component.expanded ? hit.component.toolCallId : null;
|
|
1560
|
+
} else if (visibleLine.includes(SHOW_MORE_LABEL)) {
|
|
1561
|
+
const hit = findToolAtScreenRow(tui, packet.row);
|
|
1562
|
+
const view = hit && hit.component.expanded ? resolveIoViewFromTool(hit.component) : null;
|
|
1563
|
+
const section = view?.matchShowMoreLine(visibleLine) ?? null;
|
|
1564
|
+
const box = view?.showMoreHitbox(visibleLine);
|
|
1565
|
+
if (view && section && box && packet.col >= box.startCol && packet.col <= box.endCol) {
|
|
1566
|
+
nextIoView = view;
|
|
1567
|
+
nextIoSection = section;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
const changed = nextToolCallId !== hoveredToolCallId;
|
|
1446
1572
|
hoveredToolCallId = nextToolCallId;
|
|
1447
|
-
tui.requestRender?.();
|
|
1573
|
+
if (setHoveredToolIo(nextIoView, nextIoSection) || changed) tui.requestRender?.();
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
function isExpandedToolCardHit(hit: ToolRenderHit, width: number): boolean {
|
|
1577
|
+
if (!Boolean(hit.component.expanded)) return false;
|
|
1578
|
+
const box = hit.component.contentBox;
|
|
1579
|
+
if (!box || !Array.isArray(hit.component.children) || !hit.component.children.includes(box)) {
|
|
1580
|
+
return false;
|
|
1581
|
+
}
|
|
1582
|
+
const boxLines = box.render?.(width);
|
|
1583
|
+
const componentLines = renderComponentTree(hit.component, width);
|
|
1584
|
+
if (!Array.isArray(boxLines) || boxLines.length === 0) return false;
|
|
1585
|
+
const cardStart = componentLines.length - boxLines.length;
|
|
1586
|
+
return hit.row >= cardStart && hit.row < componentLines.length;
|
|
1448
1587
|
}
|
|
1449
1588
|
|
|
1450
1589
|
function toggleToolAtMouseClick(tui: any, packet: SgrMousePacket): boolean {
|
|
1590
|
+
const line = visibleMouseLine(tui, packet);
|
|
1591
|
+
const collapsedHint = isCollapsedHintAtColumn(line, packet.col);
|
|
1592
|
+
const hasShowMore = line.includes(SHOW_MORE_LABEL);
|
|
1593
|
+
|
|
1451
1594
|
const hit = findToolAtScreenRow(tui, packet.row);
|
|
1452
1595
|
if (!hit) return false;
|
|
1453
|
-
|
|
1454
|
-
if (tryOpenToolIoShowMore(tui, packet, hit)) return true;
|
|
1455
|
-
|
|
1456
|
-
const wasExpanded = Boolean(hit.component.expanded);
|
|
1457
1596
|
const width = Math.max(1, Number(tui?.terminal?.columns) || 80);
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1597
|
+
if (Boolean(hit.component.expanded)) {
|
|
1598
|
+
if (hasShowMore && tryOpenToolIoShowMore(tui, packet, hit)) return true;
|
|
1599
|
+
if (!isExpandedToolCardHit(hit, width)) return false;
|
|
1600
|
+
const previousHeight = renderComponentTree(hit.component, width).length;
|
|
1601
|
+
hit.component.setExpanded(false);
|
|
1602
|
+
hoveredToolCallId = null;
|
|
1603
|
+
setHoveredToolIo(null, null);
|
|
1604
|
+
hit.component.invalidate?.();
|
|
1605
|
+
const nextHeight = renderComponentTree(hit.component, width).length;
|
|
1606
|
+
tui.requestRender?.();
|
|
1464
1607
|
scheduleCollapseViewportCompensation(tui, previousHeight - nextHeight, packet);
|
|
1608
|
+
return true;
|
|
1465
1609
|
}
|
|
1610
|
+
if (!collapsedHint) return false;
|
|
1611
|
+
|
|
1612
|
+
hit.component.setExpanded(true);
|
|
1613
|
+
hoveredToolCallId = null;
|
|
1614
|
+
setHoveredToolIo(null, null);
|
|
1615
|
+
hit.component.invalidate?.();
|
|
1616
|
+
tui.requestRender?.();
|
|
1466
1617
|
return true;
|
|
1467
1618
|
}
|
|
1468
1619
|
|
|
@@ -1548,8 +1699,59 @@ function restoreToolMouseInputCapture(): void {
|
|
|
1548
1699
|
toolMouseInputPatchWrapper = null;
|
|
1549
1700
|
}
|
|
1550
1701
|
|
|
1702
|
+
function restoreToolMouseRenderPatch(): void {
|
|
1703
|
+
if (
|
|
1704
|
+
toolMouseRenderPatchTui &&
|
|
1705
|
+
toolMouseRenderPatchOriginal &&
|
|
1706
|
+
toolMouseRenderPatchTui.doRender === toolMouseRenderPatchWrapper
|
|
1707
|
+
) {
|
|
1708
|
+
toolMouseRenderPatchTui.doRender = toolMouseRenderPatchOriginal;
|
|
1709
|
+
}
|
|
1710
|
+
toolMouseRenderPatchTui = null;
|
|
1711
|
+
toolMouseRenderPatchOriginal = null;
|
|
1712
|
+
toolMouseRenderPatchWrapper = null;
|
|
1713
|
+
toolMouseRawWrite = null;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
function patchToolMouseMotionAfterRender(tui: any): void {
|
|
1717
|
+
if (!useFixedEditorFeatures(tui) || toolMouseRenderPatchTui === tui) return;
|
|
1718
|
+
restoreToolMouseRenderPatch();
|
|
1719
|
+
const original = tui?.doRender;
|
|
1720
|
+
const terminal = tui?.terminal;
|
|
1721
|
+
let prototype = terminal && Object.getPrototypeOf(terminal);
|
|
1722
|
+
let rawWrite: ((...args: any[]) => unknown) | undefined;
|
|
1723
|
+
while (prototype && !rawWrite) {
|
|
1724
|
+
const candidate = Object.getOwnPropertyDescriptor(prototype, "write")?.value;
|
|
1725
|
+
if (typeof candidate === "function") rawWrite = candidate;
|
|
1726
|
+
prototype = Object.getPrototypeOf(prototype);
|
|
1727
|
+
}
|
|
1728
|
+
if (typeof original !== "function" || !rawWrite) return;
|
|
1729
|
+
|
|
1730
|
+
toolMouseRawWrite = (data) => Reflect.apply(rawWrite, terminal, [data]);
|
|
1731
|
+
const wrapper = function (this: any, ...args: any[]) {
|
|
1732
|
+
const result = Reflect.apply(original, this, args);
|
|
1733
|
+
toolMouseRawWrite?.(TOOL_MOUSE_MOTION_ENABLE);
|
|
1734
|
+
return result;
|
|
1735
|
+
};
|
|
1736
|
+
try {
|
|
1737
|
+
tui.doRender = wrapper;
|
|
1738
|
+
} catch {
|
|
1739
|
+
toolMouseRawWrite = null;
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
toolMouseRenderPatchTui = tui;
|
|
1743
|
+
toolMouseRenderPatchOriginal = original;
|
|
1744
|
+
toolMouseRenderPatchWrapper = wrapper;
|
|
1745
|
+
toolMouseRawWrite(TOOL_MOUSE_MOTION_ENABLE);
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1551
1748
|
function handleToolMouseInput(data: string): { consume: true } | undefined {
|
|
1552
1749
|
if (!toolMouseTui) return undefined;
|
|
1750
|
+
if (
|
|
1751
|
+
toolMouseInputPatchTui === toolMouseTui &&
|
|
1752
|
+
toolMouseTui.handleInput === toolMouseInputPatchWrapper
|
|
1753
|
+
)
|
|
1754
|
+
return undefined;
|
|
1553
1755
|
updateScrollButtonFromInput(toolMouseTui, data);
|
|
1554
1756
|
if (isScrollBottomInput(data)) {
|
|
1555
1757
|
if (useFixedEditorFeatures(toolMouseTui) && jumpToBottomWithoutSubmit(toolMouseTui)) {
|
|
@@ -1596,6 +1798,7 @@ function teardownToolMouseInteraction(): void {
|
|
|
1596
1798
|
toolMouseInputUnsubscribe?.();
|
|
1597
1799
|
toolMouseInputUnsubscribe = null;
|
|
1598
1800
|
hoveredToolCallId = null;
|
|
1801
|
+
setHoveredToolIo(null, null);
|
|
1599
1802
|
try {
|
|
1600
1803
|
toolMouseTui?.terminal?.write?.(TOOL_MOUSE_DISABLE);
|
|
1601
1804
|
} catch {
|
|
@@ -1607,6 +1810,7 @@ function teardownToolMouseInteraction(): void {
|
|
|
1607
1810
|
// The UI context may already have been reset during /reload.
|
|
1608
1811
|
}
|
|
1609
1812
|
restoreToolMouseInputCapture();
|
|
1813
|
+
restoreToolMouseRenderPatch();
|
|
1610
1814
|
scrollButtonVisible = false;
|
|
1611
1815
|
scrollButtonWidget = null;
|
|
1612
1816
|
pendingScrollMessages = 0;
|
|
@@ -1635,7 +1839,7 @@ export function installToolMouseInteraction(
|
|
|
1635
1839
|
toolMouseTui = tui;
|
|
1636
1840
|
if (fixedEditorFeatures) patchToolMouseInputCapture(tui);
|
|
1637
1841
|
// Motion reporting is required for hover in both native and fixed-editor layouts.
|
|
1638
|
-
tui?.terminal?.write?.(fixedEditorFeatures ? TOOL_MOUSE_ENABLE :
|
|
1842
|
+
tui?.terminal?.write?.(fixedEditorFeatures ? TOOL_MOUSE_ENABLE : TOOL_MOUSE_MOTION_ENABLE);
|
|
1639
1843
|
const widget = {
|
|
1640
1844
|
render: (width: number) => renderScrollButton(width, theme),
|
|
1641
1845
|
invalidate() {},
|
|
@@ -1646,6 +1850,12 @@ export function installToolMouseInteraction(
|
|
|
1646
1850
|
toolMouseInputUnsubscribe = ctx.ui.onTerminalInput(handleToolMouseInput);
|
|
1647
1851
|
}
|
|
1648
1852
|
|
|
1853
|
+
function refreshToolRendererComponents(tui: any): void {
|
|
1854
|
+
const tools: any[] = [];
|
|
1855
|
+
collectToolComponents(tui, tools);
|
|
1856
|
+
for (const tool of tools) tool.invalidate?.();
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1649
1859
|
function scheduleSessionRender(refresh?: () => void): void {
|
|
1650
1860
|
const tui = toolMouseTui;
|
|
1651
1861
|
if (!tui || typeof tui.requestRender !== "function") return;
|
|
@@ -1656,6 +1866,8 @@ function scheduleSessionRender(refresh?: () => void): void {
|
|
|
1656
1866
|
sessionRenderTimer = setTimeout(() => {
|
|
1657
1867
|
sessionRenderTimer = null;
|
|
1658
1868
|
if (toolMouseTui !== tui) return;
|
|
1869
|
+
patchToolMouseMotionAfterRender(tui);
|
|
1870
|
+
refreshToolRendererComponents(tui);
|
|
1659
1871
|
refresh?.();
|
|
1660
1872
|
tui.requestRender(true);
|
|
1661
1873
|
}, 0);
|
|
@@ -1663,7 +1875,7 @@ function scheduleSessionRender(refresh?: () => void): void {
|
|
|
1663
1875
|
|
|
1664
1876
|
// Bright green for success icon (truecolor ANSI escape)
|
|
1665
1877
|
const BRIGHT_GREEN = "\x1b[38;2;80;220;100m";
|
|
1666
|
-
const
|
|
1878
|
+
const ANSI_FG_RESET = "\x1b[39m";
|
|
1667
1879
|
|
|
1668
1880
|
function refreshCurrentTranscript(compactStyle: CompactStyleHooks, ctx?: any): void {
|
|
1669
1881
|
compactStyle.refresh();
|
|
@@ -2144,19 +2356,22 @@ function createCcstyleTool(
|
|
|
2144
2356
|
const rawIcon = isPending ? pendingIcon(toolName) : settledIcon(toolName, visualState);
|
|
2145
2357
|
const icon =
|
|
2146
2358
|
visualState === "success"
|
|
2147
|
-
? `${BRIGHT_GREEN}${rawIcon}${
|
|
2359
|
+
? `${BRIGHT_GREEN}${rawIcon}${ANSI_FG_RESET}`
|
|
2148
2360
|
: theme.fg(toolIconColor(context), rawIcon);
|
|
2149
2361
|
|
|
2150
2362
|
const argument = toolCallArgument(args);
|
|
2363
|
+
let cachedWidth: number | undefined;
|
|
2364
|
+
let cachedLine: string | undefined;
|
|
2151
2365
|
return {
|
|
2152
2366
|
render(width: number) {
|
|
2153
|
-
|
|
2367
|
+
if (cachedLine !== undefined && cachedWidth === width) return [cachedLine];
|
|
2368
|
+
const callWidth = Math.max(0, toolViewportWidth(width) - visibleWidth(icon) - 1);
|
|
2369
|
+
const argumentWidth = Math.max(1, callWidth - visibleWidth(`${label}()`));
|
|
2154
2370
|
const shown = argument ? middleTruncateToWidth(argument.value, argumentWidth) : undefined;
|
|
2155
2371
|
const call = shown === undefined ? label : `${label}(${shown})`;
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
return [`${icon} ${theme.fg("toolTitle", truncateToWidth(call, callWidth, "…"))}`];
|
|
2372
|
+
cachedWidth = width;
|
|
2373
|
+
cachedLine = `${icon} ${theme.fg("toolTitle", truncateToWidth(call, callWidth, ""))}`;
|
|
2374
|
+
return [cachedLine];
|
|
2160
2375
|
},
|
|
2161
2376
|
invalidate() {},
|
|
2162
2377
|
};
|
|
@@ -2195,9 +2410,12 @@ function createCcstyleTool(
|
|
|
2195
2410
|
|
|
2196
2411
|
const text = textFromResult(result);
|
|
2197
2412
|
const args = context?.args;
|
|
2198
|
-
const rendered = !expanded ? (text ? oneLine(text
|
|
2413
|
+
const rendered = !expanded ? (text ? oneLine(text) : "Done") : text;
|
|
2199
2414
|
|
|
2200
|
-
const
|
|
2415
|
+
const detailLineCount = outputLineCount(result) || countLines(formatToolInputArgs(args));
|
|
2416
|
+
const hint =
|
|
2417
|
+
!expanded && hasExpandableDetail(text, args) ? expandHint(theme, detailLineCount) : "";
|
|
2418
|
+
const hoveredHint = hint ? expandHint(theme, detailLineCount, true) : "";
|
|
2201
2419
|
if (expanded) {
|
|
2202
2420
|
return renderExpandedToolResult(
|
|
2203
2421
|
text || "",
|
|
@@ -2210,18 +2428,23 @@ function createCcstyleTool(
|
|
|
2210
2428
|
}
|
|
2211
2429
|
if (context?.state) context.state.ccstyleIoView = undefined;
|
|
2212
2430
|
const toolCallId = context?.toolCallId;
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
);
|
|
2431
|
+
let cachedWidth: number | undefined;
|
|
2432
|
+
let cachedLine: string | undefined;
|
|
2433
|
+
let cachedHoveredLine: string | undefined;
|
|
2217
2434
|
return {
|
|
2218
2435
|
render(width: number) {
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2436
|
+
if (cachedLine === undefined || cachedWidth !== width) {
|
|
2437
|
+
cachedWidth = width;
|
|
2438
|
+
cachedLine = theme.fg(
|
|
2439
|
+
isError ? "error" : "muted",
|
|
2440
|
+
renderCollapsedToolResultToWidth(rendered, hint, width),
|
|
2441
|
+
);
|
|
2442
|
+
cachedHoveredLine = theme.fg(
|
|
2443
|
+
isError ? "error" : "muted",
|
|
2444
|
+
renderCollapsedToolResultToWidth(rendered, hoveredHint, width),
|
|
2445
|
+
);
|
|
2446
|
+
}
|
|
2447
|
+
return [toolCallId && hoveredToolCallId === toolCallId ? cachedHoveredLine! : cachedLine];
|
|
2225
2448
|
},
|
|
2226
2449
|
invalidate() {},
|
|
2227
2450
|
};
|
|
@@ -2480,10 +2703,14 @@ function installGlobalToolRendering(
|
|
|
2480
2703
|
},
|
|
2481
2704
|
getRenderShell: function (this: any, ...args: any[]) {
|
|
2482
2705
|
if (!patch.active) return patch.downstream.getRenderShell.apply(this, args);
|
|
2706
|
+
const useSelfShell = shouldUseSelfShell(this, patch);
|
|
2707
|
+
const useCcstyle = shouldGloballyStyleTool(this, patch);
|
|
2483
2708
|
const shell =
|
|
2484
|
-
|
|
2709
|
+
useSelfShell || (useCcstyle && !this.expanded)
|
|
2485
2710
|
? "self"
|
|
2486
|
-
:
|
|
2711
|
+
: useCcstyle
|
|
2712
|
+
? "default"
|
|
2713
|
+
: patch.downstream.getRenderShell.apply(this, args);
|
|
2487
2714
|
syncToolShell(this, shell);
|
|
2488
2715
|
return shell;
|
|
2489
2716
|
},
|
package/package.json
CHANGED