pi-cc-extensions 0.8.20 → 0.8.22
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.
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { DynamicBorder, type Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { renderDialogHeader, renderDialogTopBorder } from "../../closable-dialog.ts";
|
|
3
2
|
import { type Component, Container, type Input, Spacer } from "@earendil-works/pi-tui";
|
|
4
3
|
import type { QuestionnaireState } from "../state/state.js";
|
|
5
4
|
import type { QuestionData } from "../tool/types.js";
|
|
@@ -134,25 +133,10 @@ export class DialogView implements StatefulView<DialogProps> {
|
|
|
134
133
|
// Render only the active tab's natural content; blank height equalization would
|
|
135
134
|
// unnecessarily consume transcript rows above this full-width editor component.
|
|
136
135
|
const natural = this.buildContainerFromStrategy(strategy, headingRowCache).render(width);
|
|
137
|
-
if (natural.length > 0) {
|
|
138
|
-
natural[0] = renderDialogTopBorder(width, (text) => this.config.theme.fg("accent", text));
|
|
139
|
-
natural.splice(
|
|
140
|
-
1,
|
|
141
|
-
0,
|
|
142
|
-
renderDialogHeader(
|
|
143
|
-
" Questions",
|
|
144
|
-
width,
|
|
145
|
-
(text) => this.config.theme.fg("accent", text),
|
|
146
|
-
(text) => this.config.theme.fg("toolTitle", text),
|
|
147
|
-
(text) => this.config.theme.fg("muted", text),
|
|
148
|
-
false,
|
|
149
|
-
),
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
136
|
|
|
153
137
|
// Fixed region sizes (deterministic from structure).
|
|
154
138
|
// TabBar.render() returns [tabLine, ""] — always 2 rows.
|
|
155
|
-
const topFixed =
|
|
139
|
+
const topFixed = 1 + (this.config.isMulti && this.config.tabBar ? 2 : 0) + 1;
|
|
156
140
|
const bottomFixed = 1 + strategy.footerRowCount;
|
|
157
141
|
const middleRows = natural.length - topFixed - bottomFixed;
|
|
158
142
|
|
|
@@ -29,12 +29,6 @@ import {
|
|
|
29
29
|
wrapTextWithAnsi,
|
|
30
30
|
} from "@earendil-works/pi-tui";
|
|
31
31
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
32
|
-
import {
|
|
33
|
-
bottomDialogBounds,
|
|
34
|
-
isDialogCloseClick,
|
|
35
|
-
renderDialogHeader,
|
|
36
|
-
renderDialogTopBorder,
|
|
37
|
-
} from "./closable-dialog.ts";
|
|
38
32
|
import { homedir } from "node:os";
|
|
39
33
|
import { join } from "node:path";
|
|
40
34
|
|
|
@@ -794,7 +788,6 @@ type ToolRenderHit = {
|
|
|
794
788
|
|
|
795
789
|
const TOOL_MOUSE_WIDGET_KEY = "ccstyle-tool-mouse";
|
|
796
790
|
const TOOL_MOUSE_ENABLE = "\x1b[?1000h\x1b[?1003h\x1b[?1006h";
|
|
797
|
-
const TOOL_MOUSE_HOVER_ENABLE = "\x1b[?1003h\x1b[?1006h";
|
|
798
791
|
const TOOL_MOUSE_DISABLE = "\x1b[?1006l\x1b[?1003l\x1b[?1000l";
|
|
799
792
|
const ZENTUI_PAGE_UP_INPUT = /^\x1b\[5;9(?::[12])?~$|^\x1b\[57421;9(?::[12])?u$|^\x1b\[1;6A$/;
|
|
800
793
|
const ZENTUI_PAGE_DOWN_INPUT = /^\x1b\[6;9(?::[12])?~$|^\x1b\[57422;9(?::[12])?u$|^\x1b\[1;6B$/;
|
|
@@ -817,7 +810,6 @@ let pendingScrollMessages = 0;
|
|
|
817
810
|
let assistantMessageActive = false;
|
|
818
811
|
let scrollButtonSyncScheduled = false;
|
|
819
812
|
let sessionRenderTimer: ReturnType<typeof setTimeout> | null = null;
|
|
820
|
-
let hoveredToolSummaryRow: number | null = null;
|
|
821
813
|
let hoveredToolCallId: string | null = null;
|
|
822
814
|
|
|
823
815
|
function parseSgrMousePackets(data: string): SgrMousePacket[] | null {
|
|
@@ -1448,19 +1440,10 @@ function tryOpenToolIoShowMore(tui: any, packet: SgrMousePacket, hit: ToolRender
|
|
|
1448
1440
|
|
|
1449
1441
|
function updateToolSummaryHover(tui: any, packet: SgrMousePacket): void {
|
|
1450
1442
|
if ((packet.code & 32) === 0 || packet.final !== "M") return;
|
|
1451
|
-
const
|
|
1452
|
-
const
|
|
1453
|
-
|
|
1454
|
-
: (Number.isFinite(tui?.previousViewportTop) ? tui.previousViewportTop : 0) + packet.row - 1;
|
|
1455
|
-
const line = stripTerminalSequences(String(previousLines[lineIndex] ?? ""));
|
|
1456
|
-
const hit = COLLAPSED_TOOL_SUMMARY.test(line) ? findToolAtScreenRow(tui, packet.row) : null;
|
|
1457
|
-
const nextRow = hit && !hit.component.expanded ? packet.row : null;
|
|
1458
|
-
const nextToolCallId = nextRow ? (hit?.component?.toolCallId ?? null) : null;
|
|
1459
|
-
if (nextRow === hoveredToolSummaryRow && nextToolCallId === hoveredToolCallId) return;
|
|
1460
|
-
hoveredToolSummaryRow = nextRow;
|
|
1443
|
+
const hit = findToolAtScreenRow(tui, packet.row);
|
|
1444
|
+
const nextToolCallId = hit && !hit.component.expanded ? hit.component.toolCallId : null;
|
|
1445
|
+
if (nextToolCallId === hoveredToolCallId) return;
|
|
1461
1446
|
hoveredToolCallId = nextToolCallId;
|
|
1462
|
-
// Collapsed result components read hoveredToolCallId during render. Keeping
|
|
1463
|
-
// hover in the render path avoids an async TUI repaint overwriting raw ANSI.
|
|
1464
1447
|
tui.requestRender?.();
|
|
1465
1448
|
}
|
|
1466
1449
|
|
|
@@ -1612,7 +1595,6 @@ function teardownToolMouseInteraction(): void {
|
|
|
1612
1595
|
}
|
|
1613
1596
|
toolMouseInputUnsubscribe?.();
|
|
1614
1597
|
toolMouseInputUnsubscribe = null;
|
|
1615
|
-
hoveredToolSummaryRow = null;
|
|
1616
1598
|
hoveredToolCallId = null;
|
|
1617
1599
|
try {
|
|
1618
1600
|
toolMouseTui?.terminal?.write?.(TOOL_MOUSE_DISABLE);
|
|
@@ -1653,7 +1635,7 @@ export function installToolMouseInteraction(
|
|
|
1653
1635
|
toolMouseTui = tui;
|
|
1654
1636
|
if (fixedEditorFeatures) patchToolMouseInputCapture(tui);
|
|
1655
1637
|
// Motion reporting is required for hover in both native and fixed-editor layouts.
|
|
1656
|
-
tui?.terminal?.write?.(fixedEditorFeatures ? TOOL_MOUSE_ENABLE :
|
|
1638
|
+
tui?.terminal?.write?.(fixedEditorFeatures ? TOOL_MOUSE_ENABLE : "\x1b[?1003h\x1b[?1006h");
|
|
1657
1639
|
const widget = {
|
|
1658
1640
|
render: (width: number) => renderScrollButton(width, theme),
|
|
1659
1641
|
invalidate() {},
|
|
@@ -2004,8 +1986,6 @@ async function showCcstylePanel(ctx: any, compactStyle: CompactStyleHooks): Prom
|
|
|
2004
1986
|
];
|
|
2005
1987
|
|
|
2006
1988
|
let activeSection = 0;
|
|
2007
|
-
let lastWidth = 0;
|
|
2008
|
-
let lastHeight = 0;
|
|
2009
1989
|
const settingsTheme = getSettingsListTheme();
|
|
2010
1990
|
const lists = sections.map(
|
|
2011
1991
|
(section) =>
|
|
@@ -2042,15 +2022,8 @@ async function showCcstylePanel(ctx: any, compactStyle: CompactStyleHooks): Prom
|
|
|
2042
2022
|
while (listBody.length > 0 && listBody[listBody.length - 1] === "") listBody.pop();
|
|
2043
2023
|
|
|
2044
2024
|
// Frame: top rule · tabs · mid rule · settings · mid rule · footer · bottom rule
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
renderDialogHeader(
|
|
2048
|
-
" CC Style",
|
|
2049
|
-
safeWidth,
|
|
2050
|
-
(text) => theme.fg("border", text),
|
|
2051
|
-
(text) => theme.fg("toolTitle", text),
|
|
2052
|
-
(text) => theme.fg("muted", text),
|
|
2053
|
-
),
|
|
2025
|
+
return [
|
|
2026
|
+
rule,
|
|
2054
2027
|
renderSectionTabBar(theme, sections, activeSection, safeWidth),
|
|
2055
2028
|
rule,
|
|
2056
2029
|
...listBody,
|
|
@@ -2064,22 +2037,11 @@ async function showCcstylePanel(ctx: any, compactStyle: CompactStyleHooks): Prom
|
|
|
2064
2037
|
),
|
|
2065
2038
|
rule,
|
|
2066
2039
|
];
|
|
2067
|
-
lastWidth = safeWidth;
|
|
2068
|
-
lastHeight = lines.length;
|
|
2069
|
-
return lines;
|
|
2070
2040
|
},
|
|
2071
2041
|
invalidate() {
|
|
2072
2042
|
for (const list of lists) list.invalidate();
|
|
2073
2043
|
},
|
|
2074
2044
|
handleInput(data: string) {
|
|
2075
|
-
if (
|
|
2076
|
-
!excludeSubmenuOpen &&
|
|
2077
|
-
tui.terminal &&
|
|
2078
|
-
isDialogCloseClick(data, bottomDialogBounds(tui.terminal, lastWidth, lastHeight))
|
|
2079
|
-
) {
|
|
2080
|
-
done();
|
|
2081
|
-
return;
|
|
2082
|
-
}
|
|
2083
2045
|
if (!excludeSubmenuOpen && isForwardTabKey(data)) {
|
|
2084
2046
|
switchSection(1);
|
|
2085
2047
|
tui.requestRender();
|
|
@@ -2130,15 +2092,22 @@ function toolCallArgument(args: any): { key: string; value: string } | undefined
|
|
|
2130
2092
|
: undefined;
|
|
2131
2093
|
}
|
|
2132
2094
|
|
|
2133
|
-
function
|
|
2095
|
+
export function middleTruncateToWidth(text: string, width: number): string {
|
|
2134
2096
|
if (visibleWidth(text) <= width) return text;
|
|
2135
2097
|
if (width <= 1) return "…";
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2098
|
+
const chars = Array.from(text);
|
|
2099
|
+
const leftWidth = Math.ceil((width - 1) / 2);
|
|
2100
|
+
let left = "";
|
|
2101
|
+
let right = "";
|
|
2102
|
+
for (const char of chars) {
|
|
2103
|
+
if (visibleWidth(left + char) > leftWidth) break;
|
|
2104
|
+
left += char;
|
|
2105
|
+
}
|
|
2106
|
+
for (const char of chars.reverse()) {
|
|
2107
|
+
if (visibleWidth(left + "…" + char + right) > width) break;
|
|
2108
|
+
right = char + right;
|
|
2140
2109
|
}
|
|
2141
|
-
return
|
|
2110
|
+
return `${left}…${right}`;
|
|
2142
2111
|
}
|
|
2143
2112
|
|
|
2144
2113
|
export function shouldRenderRichDiff(
|
|
@@ -2182,11 +2151,7 @@ function createCcstyleTool(
|
|
|
2182
2151
|
return {
|
|
2183
2152
|
render(width: number) {
|
|
2184
2153
|
const argumentWidth = Math.max(1, width - visibleWidth(`${rawIcon} ${label}()`));
|
|
2185
|
-
const shown = argument
|
|
2186
|
-
? argument.key === "path"
|
|
2187
|
-
? tailToWidth(argument.value, argumentWidth)
|
|
2188
|
-
: truncateToWidth(argument.value, argumentWidth, "…")
|
|
2189
|
-
: undefined;
|
|
2154
|
+
const shown = argument ? middleTruncateToWidth(argument.value, argumentWidth) : undefined;
|
|
2190
2155
|
const call = shown === undefined ? label : `${label}(${shown})`;
|
|
2191
2156
|
// Leave one cell unused so the host shell never performs a second,
|
|
2192
2157
|
// unthemed truncation after this renderer has applied toolTitle.
|
|
@@ -2244,8 +2209,7 @@ function createCcstyleTool(
|
|
|
2244
2209
|
);
|
|
2245
2210
|
}
|
|
2246
2211
|
if (context?.state) context.state.ccstyleIoView = undefined;
|
|
2247
|
-
const toolCallId =
|
|
2248
|
-
context?.toolCallId ?? context?.id ?? context?.state?.toolCallId ?? context?.state?.id;
|
|
2212
|
+
const toolCallId = context?.toolCallId;
|
|
2249
2213
|
const summary = theme.fg(
|
|
2250
2214
|
isError ? "error" : "muted",
|
|
2251
2215
|
renderCollapsedToolResult(rendered, hint),
|
|
@@ -2254,8 +2218,10 @@ function createCcstyleTool(
|
|
|
2254
2218
|
render(width: number) {
|
|
2255
2219
|
const line = truncateToWidth(summary, width, "…");
|
|
2256
2220
|
if (!toolCallId || hoveredToolCallId !== toolCallId) return [line];
|
|
2257
|
-
const
|
|
2258
|
-
return [
|
|
2221
|
+
const highlighted = line.replace(/\x1b\[0m/g, "\x1b[0;7m");
|
|
2222
|
+
return [
|
|
2223
|
+
`\x1b[7m${highlighted}${" ".repeat(Math.max(0, width - visibleWidth(line)))}\x1b[0m`,
|
|
2224
|
+
];
|
|
2259
2225
|
},
|
|
2260
2226
|
invalidate() {},
|
|
2261
2227
|
};
|
package/extensions/context.ts
CHANGED
|
@@ -10,12 +10,6 @@ import {
|
|
|
10
10
|
visibleWidth,
|
|
11
11
|
wrapTextWithAnsi,
|
|
12
12
|
} from "@earendil-works/pi-tui";
|
|
13
|
-
import {
|
|
14
|
-
centeredDialogBounds,
|
|
15
|
-
isDialogCloseClick,
|
|
16
|
-
renderDialogHeader,
|
|
17
|
-
renderDialogTopBorder,
|
|
18
|
-
} from "./closable-dialog.ts";
|
|
19
13
|
|
|
20
14
|
export type ContextPart = {
|
|
21
15
|
label: string;
|
|
@@ -71,8 +65,6 @@ export async function showTextPreview(
|
|
|
71
65
|
let scrollOffset = 0;
|
|
72
66
|
let pageSize = 1;
|
|
73
67
|
let totalLines = 1;
|
|
74
|
-
let lastWidth = 0;
|
|
75
|
-
let lastHeight = 0;
|
|
76
68
|
const wrappedContent = createWrappedTextCache(content);
|
|
77
69
|
|
|
78
70
|
const scrollTo = (nextOffset: number): void => {
|
|
@@ -85,10 +77,6 @@ export async function showTextPreview(
|
|
|
85
77
|
wrappedContent.invalidate();
|
|
86
78
|
},
|
|
87
79
|
handleInput(data: string) {
|
|
88
|
-
if (isDialogCloseClick(data, centeredDialogBounds(tui.terminal, lastWidth, lastHeight))) {
|
|
89
|
-
done(undefined);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
80
|
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) {
|
|
93
81
|
done(undefined);
|
|
94
82
|
return;
|
|
@@ -150,24 +138,15 @@ export async function showTextPreview(
|
|
|
150
138
|
const end = Math.min(totalLines, scrollOffset + pageSize);
|
|
151
139
|
const status = `${start}-${end} / ${totalLines} lines · ↑↓ PgUp/PgDn Home/End · Esc close`;
|
|
152
140
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
` ${title}`,
|
|
157
|
-
width,
|
|
158
|
-
border,
|
|
159
|
-
(text) => theme.bold(theme.fg("accent", text)),
|
|
160
|
-
(text) => theme.fg("muted", text),
|
|
161
|
-
),
|
|
141
|
+
return [
|
|
142
|
+
border(`╭${"─".repeat(inner)}╮`),
|
|
143
|
+
`${border("│")}${padLine(` ${theme.bold(theme.fg("accent", title))}`)}${border("│")}`,
|
|
162
144
|
`${border("├")}${border("─".repeat(inner))}${border("┤")}`,
|
|
163
145
|
...bodyRows,
|
|
164
146
|
`${border("├")}${border("─".repeat(inner))}${border("┤")}`,
|
|
165
147
|
`${border("│")}${padLine(theme.fg("dim", ` ${status}`))}${border("│")}`,
|
|
166
148
|
border(`╰${"─".repeat(inner)}╯`),
|
|
167
149
|
];
|
|
168
|
-
lastWidth = width;
|
|
169
|
-
lastHeight = lines.length;
|
|
170
|
-
return lines;
|
|
171
150
|
},
|
|
172
151
|
};
|
|
173
152
|
},
|
package/package.json
CHANGED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
2
|
-
|
|
3
|
-
export type DialogBounds = { left: number; top: number; width: number };
|
|
4
|
-
|
|
5
|
-
export function renderDialogTopBorder(width: number, color: (text: string) => string): string {
|
|
6
|
-
const safeWidth = Math.max(4, Math.floor(width));
|
|
7
|
-
return color(`╭${"─".repeat(safeWidth - 2)}╮`);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/** Render title and a larger [ × ] target on the row below the top border. */
|
|
11
|
-
export function renderDialogHeader(
|
|
12
|
-
title: string,
|
|
13
|
-
width: number,
|
|
14
|
-
color: (text: string) => string,
|
|
15
|
-
titleColor: (text: string) => string,
|
|
16
|
-
closeColor: (text: string) => string,
|
|
17
|
-
showClose = true,
|
|
18
|
-
): string {
|
|
19
|
-
const safeWidth = Math.max(8, Math.floor(width));
|
|
20
|
-
const innerWidth = safeWidth - 2;
|
|
21
|
-
const button = showClose ? "[ × ]" : "";
|
|
22
|
-
const titleWidth = Math.max(0, innerWidth - visibleWidth(button) - (showClose ? 2 : 0));
|
|
23
|
-
const shownTitle = truncateToWidth(title, titleWidth, "…");
|
|
24
|
-
const gap = " ".repeat(
|
|
25
|
-
Math.max(showClose ? 1 : 0, innerWidth - visibleWidth(shownTitle) - visibleWidth(button)),
|
|
26
|
-
);
|
|
27
|
-
return `${color("│")}${titleColor(shownTitle)}${gap}${showClose ? closeColor(button) : ""}${color("│")}`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Match a left-button press anywhere on the [ × ] target. */
|
|
31
|
-
export function isDialogCloseClick(data: string, bounds: DialogBounds): boolean {
|
|
32
|
-
const match = data.match(/^\x1b\[<(\d+);(\d+);(\d+)([Mm])$/);
|
|
33
|
-
if (!match || match[4] !== "M") return false;
|
|
34
|
-
const code = Number(match[1]);
|
|
35
|
-
if ((code & 3) !== 0 || (code & 32) !== 0) return false;
|
|
36
|
-
const col = Number(match[2]);
|
|
37
|
-
const firstButtonCol = bounds.left + bounds.width - 7;
|
|
38
|
-
const lastButtonCol = bounds.left + bounds.width - 3;
|
|
39
|
-
return col >= firstButtonCol && col <= lastButtonCol && Number(match[3]) === bounds.top + 1;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function centeredDialogBounds(
|
|
43
|
-
terminal: { columns: number; rows: number },
|
|
44
|
-
width: number,
|
|
45
|
-
height: number,
|
|
46
|
-
): DialogBounds {
|
|
47
|
-
return {
|
|
48
|
-
left: Math.floor((terminal.columns - width) / 2) + 1,
|
|
49
|
-
top: Math.floor((terminal.rows - height) / 2) + 1,
|
|
50
|
-
width,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function bottomDialogBounds(
|
|
55
|
-
terminal: { rows: number },
|
|
56
|
-
width: number,
|
|
57
|
-
height: number,
|
|
58
|
-
): DialogBounds {
|
|
59
|
-
return { left: 1, top: Math.max(1, terminal.rows - height + 1), width };
|
|
60
|
-
}
|