pi-cc-extensions 0.5.5 → 0.5.6
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 +151 -7
- package/package.json +1 -1
|
@@ -318,6 +318,8 @@ const TOOL_MOUSE_WIDGET_KEY = "ccstyle-tool-mouse";
|
|
|
318
318
|
const TOOL_MOUSE_ENABLE = "\x1b[?1000h\x1b[?1006h";
|
|
319
319
|
const TOOL_MOUSE_DISABLE = "\x1b[?1006l\x1b[?1000l";
|
|
320
320
|
const ZENTUI_PAGE_UP_INPUT = /^\x1b\[5;9(?::[12])?~$|^\x1b\[57421;9(?::[12])?u$|^\x1b\[1;6A$/;
|
|
321
|
+
const ZENTUI_PAGE_DOWN_INPUT = /^\x1b\[6;9(?::[12])?~$|^\x1b\[57422;9(?::[12])?u$|^\x1b\[1;6B$/;
|
|
322
|
+
const SCROLL_BOTTOM_SHORTCUT = "ctrl+end";
|
|
321
323
|
let toolMouseTui: any = null;
|
|
322
324
|
let toolMouseUi: any = null;
|
|
323
325
|
let toolMouseInputUnsubscribe: (() => void) | null = null;
|
|
@@ -326,6 +328,9 @@ let toolMouseInputPatchOriginalHandle: ((...args: any[]) => any) | null = null;
|
|
|
326
328
|
let toolMouseInputPatchWrapper: ((...args: any[]) => any) | null = null;
|
|
327
329
|
let scrollButtonVisible = false;
|
|
328
330
|
let scrollButtonWidget: any = null;
|
|
331
|
+
let pendingScrollMessages = 0;
|
|
332
|
+
let assistantMessageActive = false;
|
|
333
|
+
let scrollButtonSyncScheduled = false;
|
|
329
334
|
|
|
330
335
|
function parseSgrMousePackets(data: string): SgrMousePacket[] | null {
|
|
331
336
|
const pattern = /\x1b\[<(\d+);(\d+);(\d+)([Mm])/g;
|
|
@@ -520,6 +525,96 @@ function isFixedEditorTui(tui: any): boolean {
|
|
|
520
525
|
return typeof ownRows?.get === "function" && ownRows.get !== inheritedRows?.get;
|
|
521
526
|
}
|
|
522
527
|
|
|
528
|
+
function formatShortcut(shortcut: string): string {
|
|
529
|
+
return shortcut
|
|
530
|
+
.split("+")
|
|
531
|
+
.map((part) => part.length <= 1 ? part.toUpperCase() : `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`)
|
|
532
|
+
.join("+");
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function isScrollBottomInput(data: string): boolean {
|
|
536
|
+
return matchesKey(data, SCROLL_BOTTOM_SHORTCUT);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function isScrollNavigationInput(data: string): boolean {
|
|
540
|
+
if (
|
|
541
|
+
matchesKey(data, "pageUp")
|
|
542
|
+
|| matchesKey(data, "pageDown")
|
|
543
|
+
|| ZENTUI_PAGE_UP_INPUT.test(data)
|
|
544
|
+
|| ZENTUI_PAGE_DOWN_INPUT.test(data)
|
|
545
|
+
) {
|
|
546
|
+
return true;
|
|
547
|
+
}
|
|
548
|
+
const packets = parseSgrMousePackets(data);
|
|
549
|
+
return Boolean(packets?.some((packet) => {
|
|
550
|
+
const baseButton = packet.code & ~(4 | 8 | 16 | 32);
|
|
551
|
+
return packet.final === "M" && (baseButton === 64 || baseButton === 65);
|
|
552
|
+
}));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function directRenderLines(component: any, width: number): string[] {
|
|
556
|
+
try {
|
|
557
|
+
const lines = component?.render?.(width);
|
|
558
|
+
return Array.isArray(lines) ? lines : [];
|
|
559
|
+
} catch {
|
|
560
|
+
return [];
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/** Render only the transcript portion that fixed-editor leaves scrollable. */
|
|
565
|
+
function renderFixedScrollableRoot(tui: any, width: number): string[] {
|
|
566
|
+
const children = Array.isArray(tui?.children) ? tui.children : [];
|
|
567
|
+
const editorIndex = children.findIndex((child: any) =>
|
|
568
|
+
containsEditorLike(child, tui.focusedComponent),
|
|
569
|
+
);
|
|
570
|
+
const end = editorIndex >= 2 ? editorIndex - 2 : children.length;
|
|
571
|
+
return children.slice(0, end).flatMap((child: any) => directRenderLines(child, width));
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function isFixedEditorAtBottom(tui: any): boolean {
|
|
575
|
+
if (!isFixedEditorTui(tui)) return true;
|
|
576
|
+
const visibleLines = Array.isArray(tui?.previousLines) ? tui.previousLines : [];
|
|
577
|
+
if (visibleLines.length === 0) return true;
|
|
578
|
+
const width = Math.max(1, Number(tui?.terminal?.columns) || 80);
|
|
579
|
+
const rootLines = renderFixedScrollableRoot(tui, width);
|
|
580
|
+
if (rootLines.length <= visibleLines.length) return true;
|
|
581
|
+
const tail = rootLines.slice(-visibleLines.length);
|
|
582
|
+
return tail.every((line, index) =>
|
|
583
|
+
stripTerminalSequences(String(line)) === stripTerminalSequences(String(visibleLines[index] ?? "")),
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function hideScrollButton(tui: any): void {
|
|
588
|
+
const changed = scrollButtonVisible || pendingScrollMessages > 0;
|
|
589
|
+
scrollButtonVisible = false;
|
|
590
|
+
pendingScrollMessages = 0;
|
|
591
|
+
if (changed) tui.requestRender?.();
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function scheduleScrollButtonSync(tui: any, data: string): void {
|
|
595
|
+
if (!scrollButtonVisible || !isScrollNavigationInput(data) || scrollButtonSyncScheduled) return;
|
|
596
|
+
scrollButtonSyncScheduled = true;
|
|
597
|
+
const previousLines = tui.previousLines;
|
|
598
|
+
const check = (attempt: number) => {
|
|
599
|
+
scrollButtonSyncScheduled = false;
|
|
600
|
+
if (toolMouseTui !== tui || !scrollButtonVisible) return;
|
|
601
|
+
// Pi renders on its own frame timer. Do not inspect the old visible window
|
|
602
|
+
// before Zentui has applied the new scroll offset, or PageUp at bottom would
|
|
603
|
+
// immediately hide the button it just requested.
|
|
604
|
+
const rendered = tui.previousLines !== previousLines;
|
|
605
|
+
if (!rendered && attempt < 4) {
|
|
606
|
+
scrollButtonSyncScheduled = true;
|
|
607
|
+
const timer = setTimeout(() => check(attempt + 1), 16);
|
|
608
|
+
if (typeof timer === "object" && timer !== null && "unref" in timer) {
|
|
609
|
+
(timer as { unref: () => void }).unref();
|
|
610
|
+
}
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
if (isFixedEditorAtBottom(tui)) hideScrollButton(tui);
|
|
614
|
+
};
|
|
615
|
+
process.nextTick(() => check(0));
|
|
616
|
+
}
|
|
617
|
+
|
|
523
618
|
function updateScrollButtonFromInput(tui: any, data: string): void {
|
|
524
619
|
if (!isFixedEditorTui(tui)) return;
|
|
525
620
|
|
|
@@ -532,8 +627,15 @@ function updateScrollButtonFromInput(tui: any, data: string): void {
|
|
|
532
627
|
});
|
|
533
628
|
}
|
|
534
629
|
|
|
535
|
-
const jumpedBottom =
|
|
536
|
-
|
|
630
|
+
const jumpedBottom =
|
|
631
|
+
matchesKey(data, "enter")
|
|
632
|
+
|| matchesKey(data, "return")
|
|
633
|
+
|| isScrollBottomInput(data);
|
|
634
|
+
if (jumpedBottom) {
|
|
635
|
+
hideScrollButton(tui);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
const nextVisible = movedUp ? true : scrollButtonVisible;
|
|
537
639
|
if (nextVisible !== scrollButtonVisible) {
|
|
538
640
|
scrollButtonVisible = nextVisible;
|
|
539
641
|
tui.requestRender?.();
|
|
@@ -692,8 +794,7 @@ function jumpToBottomWithoutSubmit(tui: any): boolean {
|
|
|
692
794
|
} finally {
|
|
693
795
|
tui.focusedComponent = focused;
|
|
694
796
|
}
|
|
695
|
-
|
|
696
|
-
tui.requestRender?.();
|
|
797
|
+
hideScrollButton(tui);
|
|
697
798
|
return true;
|
|
698
799
|
}
|
|
699
800
|
|
|
@@ -715,7 +816,11 @@ function toggleToolAtMouseClick(tui: any, packet: SgrMousePacket): boolean {
|
|
|
715
816
|
|
|
716
817
|
function renderScrollButton(width: number, theme: any): string[] {
|
|
717
818
|
if (!scrollButtonVisible || !isFixedEditorTui(toolMouseTui)) return [];
|
|
718
|
-
const
|
|
819
|
+
const shortcut = formatShortcut(SCROLL_BOTTOM_SHORTCUT);
|
|
820
|
+
const messageText = pendingScrollMessages > 0
|
|
821
|
+
? `${pendingScrollMessages} new message${pendingScrollMessages === 1 ? "" : "s"}`
|
|
822
|
+
: "Back to bottom";
|
|
823
|
+
const label = theme.fg("accent", `[ ↓ ${messageText} · ${shortcut} · click ]`);
|
|
719
824
|
const leftPad = Math.max(0, Math.floor((width - visibleWidth(label)) / 2));
|
|
720
825
|
return [`${" ".repeat(leftPad)}${truncateToWidth(label, width, "…")}`];
|
|
721
826
|
}
|
|
@@ -737,6 +842,7 @@ function patchToolMouseInputCapture(tui: any): void {
|
|
|
737
842
|
const data = args[0];
|
|
738
843
|
if (typeof data === "string") {
|
|
739
844
|
updateScrollButtonFromInput(this, data);
|
|
845
|
+
if (isFixedEditorTui(this) && isScrollBottomInput(data) && jumpToBottomWithoutSubmit(this)) return;
|
|
740
846
|
const packets = parseSgrMousePackets(data);
|
|
741
847
|
if (packets) {
|
|
742
848
|
for (const packet of packets) {
|
|
@@ -745,7 +851,9 @@ function patchToolMouseInputCapture(tui: any): void {
|
|
|
745
851
|
}
|
|
746
852
|
}
|
|
747
853
|
}
|
|
748
|
-
|
|
854
|
+
const result = Reflect.apply(originalHandle, this, args);
|
|
855
|
+
if (typeof data === "string") scheduleScrollButtonSync(this, data);
|
|
856
|
+
return result;
|
|
749
857
|
};
|
|
750
858
|
|
|
751
859
|
try {
|
|
@@ -774,8 +882,14 @@ function restoreToolMouseInputCapture(): void {
|
|
|
774
882
|
function handleToolMouseInput(data: string): { consume: true } | undefined {
|
|
775
883
|
if (!toolMouseTui) return undefined;
|
|
776
884
|
updateScrollButtonFromInput(toolMouseTui, data);
|
|
885
|
+
if (isFixedEditorTui(toolMouseTui) && isScrollBottomInput(data) && jumpToBottomWithoutSubmit(toolMouseTui)) {
|
|
886
|
+
return { consume: true };
|
|
887
|
+
}
|
|
777
888
|
const packets = parseSgrMousePackets(data);
|
|
778
|
-
if (!packets)
|
|
889
|
+
if (!packets) {
|
|
890
|
+
scheduleScrollButtonSync(toolMouseTui, data);
|
|
891
|
+
return undefined;
|
|
892
|
+
}
|
|
779
893
|
|
|
780
894
|
let consumed = false;
|
|
781
895
|
for (const packet of packets) {
|
|
@@ -787,6 +901,7 @@ function handleToolMouseInput(data: string): { consume: true } | undefined {
|
|
|
787
901
|
|
|
788
902
|
// Let scrolling, motion, release, and clicks outside tool results reach the
|
|
789
903
|
// normal TUI input chain (including other extensions such as pi-zentui).
|
|
904
|
+
scheduleScrollButtonSync(toolMouseTui, data);
|
|
790
905
|
return consumed ? { consume: true } : undefined;
|
|
791
906
|
}
|
|
792
907
|
|
|
@@ -806,6 +921,9 @@ function teardownToolMouseInteraction(): void {
|
|
|
806
921
|
restoreToolMouseInputCapture();
|
|
807
922
|
scrollButtonVisible = false;
|
|
808
923
|
scrollButtonWidget = null;
|
|
924
|
+
pendingScrollMessages = 0;
|
|
925
|
+
assistantMessageActive = false;
|
|
926
|
+
scrollButtonSyncScheduled = false;
|
|
809
927
|
toolMouseTui = null;
|
|
810
928
|
toolMouseUi = null;
|
|
811
929
|
}
|
|
@@ -1369,6 +1487,18 @@ function registerWrappedTool(pi: ExtensionAPI, name: keyof ReturnType<typeof cre
|
|
|
1369
1487
|
});
|
|
1370
1488
|
}
|
|
1371
1489
|
|
|
1490
|
+
function notePendingScrollMessage(role: unknown): void {
|
|
1491
|
+
if (!toolMouseTui || !isFixedEditorTui(toolMouseTui) || !scrollButtonVisible) return;
|
|
1492
|
+
if (role === "assistant") {
|
|
1493
|
+
if (assistantMessageActive) return;
|
|
1494
|
+
assistantMessageActive = true;
|
|
1495
|
+
} else if (role !== "toolResult") {
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
pendingScrollMessages += 1;
|
|
1499
|
+
toolMouseTui.requestRender?.();
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1372
1502
|
export default function (pi: ExtensionAPI) {
|
|
1373
1503
|
installGlobalToolRendering();
|
|
1374
1504
|
deactivateLegacyCompactionRendering();
|
|
@@ -1423,10 +1553,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
1423
1553
|
|
|
1424
1554
|
pi.on("session_start", async (_event, ctx) => {
|
|
1425
1555
|
diffPreviewByCall.clear();
|
|
1556
|
+
pendingScrollMessages = 0;
|
|
1557
|
+
assistantMessageActive = false;
|
|
1426
1558
|
updateStatus(ctx);
|
|
1427
1559
|
installToolMouseInteraction(ctx);
|
|
1428
1560
|
});
|
|
1429
1561
|
|
|
1562
|
+
pi.on("message_start", async (event) => {
|
|
1563
|
+
notePendingScrollMessage(event?.message?.role);
|
|
1564
|
+
});
|
|
1565
|
+
|
|
1566
|
+
pi.on("message_update", async (event) => {
|
|
1567
|
+
if (event?.message?.role === "assistant") notePendingScrollMessage("assistant");
|
|
1568
|
+
});
|
|
1569
|
+
|
|
1570
|
+
pi.on("message_end", async (event) => {
|
|
1571
|
+
if (event?.message?.role === "assistant") assistantMessageActive = false;
|
|
1572
|
+
});
|
|
1573
|
+
|
|
1430
1574
|
pi.on("session_shutdown", async () => {
|
|
1431
1575
|
teardownToolMouseInteraction();
|
|
1432
1576
|
deactivateGlobalToolRendering();
|