@pi-kaush/pi-tool-call-markers 0.2.11 → 0.3.0
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/CHANGELOG.md +12 -0
- package/README.md +15 -0
- package/package.json +1 -1
- package/src/index.ts +61 -37
- package/src/muted.ts +80 -0
- package/src/thinking-block-merger.ts +36 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Color collapsed rows (tool calls, `+ Thought`, subagents) from the
|
|
6
|
+
theme's `syntaxComment` token — which ships with every theme — instead of
|
|
7
|
+
the louder muted/toolTitle/toolOutput split. The bolded tool name and the
|
|
8
|
+
call content share the single tone, so collapsed blocks read as one
|
|
9
|
+
muted unit under any theme without per-theme tuning. Failures stay
|
|
10
|
+
error-colored and live spinners keep their thinking-level tint.
|
|
11
|
+
- Add optional `collapsedToolCall` and `collapsedThinkingCall` theme color
|
|
12
|
+
tokens to override each collapsed kind independently; themes without
|
|
13
|
+
them are unchanged.
|
|
14
|
+
- Fix settled "+ Thought" labels keeping stale colors after a mid-session
|
|
15
|
+
theme switch: label styles now recompute whenever the theme object
|
|
16
|
+
changes instead of only on session start and thinking-level selects.
|
|
5
17
|
- Add `/toggle-info`: hide tool calls and thinking entirely, or restore them
|
|
6
18
|
collapsed. Execution rows (including user-run `!` blocks) are lifted out of
|
|
7
19
|
the render pass via the shared chat-container hook; thinking blocks are
|
package/README.md
CHANGED
|
@@ -100,6 +100,21 @@ pi
|
|
|
100
100
|
|
|
101
101
|
`0`, `false`, `no`, and `off` disable parallel grouping. `1`, `true`, `yes`, and `on` enable it. The value is read when the extension loads.
|
|
102
102
|
|
|
103
|
+
### Collapsed-row colors
|
|
104
|
+
|
|
105
|
+
Collapsed rows (tool calls, `+ Thought`, subagents) default to the theme's `syntaxComment` color — it ships with every Pi theme and reads as a muted tone — with the bolded tool name and the call content sharing it. Failures stay error-colored and live spinners keep their existing tints.
|
|
106
|
+
|
|
107
|
+
A theme can override each collapsed kind independently with optional color tokens:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"colors": {
|
|
112
|
+
"collapsedToolCall": "#6272a4",
|
|
113
|
+
"collapsedThinkingCall": "#6272a4"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
103
118
|
## Compatibility and fallback policy
|
|
104
119
|
|
|
105
120
|
**Compatible Pi version:** `@earendil-works/pi-coding-agent` and `@earendil-works/pi-tui` `>=0.80.6`.
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from "./bash-block.ts";
|
|
17
17
|
import { runChatContainerHooks } from "./container-hooks.ts";
|
|
18
18
|
import { installInfoVisibility } from "./info-visibility.ts";
|
|
19
|
+
import { fgCollapsed } from "./muted.ts";
|
|
19
20
|
|
|
20
21
|
const OUTER_INSET = 2;
|
|
21
22
|
const GROUP_MARKER = "%";
|
|
@@ -29,6 +30,8 @@ const COLLAPSE_PARALLEL_ENV = "PI_TOOL_CALL_MARKERS_COLLAPSE_PARALLEL";
|
|
|
29
30
|
type ThemeLike = {
|
|
30
31
|
bold(text: string): string;
|
|
31
32
|
fg(color: string, text: string): string;
|
|
33
|
+
getFgAnsi?(color: string): string;
|
|
34
|
+
getColorMode?(): "truecolor" | "256color" | string;
|
|
32
35
|
};
|
|
33
36
|
|
|
34
37
|
type ComponentLike = {
|
|
@@ -252,7 +255,7 @@ function capFailureTail(tail: string, cap: number, theme: ThemeLike): string {
|
|
|
252
255
|
}
|
|
253
256
|
|
|
254
257
|
function renderedGenericOutcome(theme: ThemeLike): string {
|
|
255
|
-
return theme
|
|
258
|
+
return fgCollapsed(theme, "toolOutput", "→ done");
|
|
256
259
|
}
|
|
257
260
|
|
|
258
261
|
// The MCP adapter stashes its call's first line in renderer state before
|
|
@@ -637,7 +640,7 @@ function renderedEditDiffLines(
|
|
|
637
640
|
for (const line of raw.slice(0, MAX_EDIT_DIFF_LINES)) {
|
|
638
641
|
const clean = sanitizeInline(line).trimEnd();
|
|
639
642
|
if (clean.trim() === "...") {
|
|
640
|
-
lines.push(theme
|
|
643
|
+
lines.push(fgCollapsed(theme, "muted", " ..."));
|
|
641
644
|
continue;
|
|
642
645
|
}
|
|
643
646
|
const match = EDIT_DIFF_LINE_RE.exec(clean);
|
|
@@ -652,7 +655,11 @@ function renderedEditDiffLines(
|
|
|
652
655
|
}
|
|
653
656
|
if (raw.length > MAX_EDIT_DIFF_LINES) {
|
|
654
657
|
lines.push(
|
|
655
|
-
|
|
658
|
+
fgCollapsed(
|
|
659
|
+
theme,
|
|
660
|
+
"muted",
|
|
661
|
+
` ... +${raw.length - MAX_EDIT_DIFF_LINES} more`,
|
|
662
|
+
),
|
|
656
663
|
);
|
|
657
664
|
}
|
|
658
665
|
return lines;
|
|
@@ -706,7 +713,7 @@ function renderedOutcome(
|
|
|
706
713
|
): string | undefined {
|
|
707
714
|
const summary = outcomeSummary(row);
|
|
708
715
|
if (!summary) return undefined;
|
|
709
|
-
return theme
|
|
716
|
+
return fgCollapsed(theme, "toolOutput", `→ ${summary}`);
|
|
710
717
|
}
|
|
711
718
|
|
|
712
719
|
function renderedGroupedOutcome(
|
|
@@ -835,10 +842,10 @@ function renderedCallSummary(
|
|
|
835
842
|
.map(trimRenderedLine)
|
|
836
843
|
.filter(hasVisibleContent);
|
|
837
844
|
if (visibleLines.length > rendered.length)
|
|
838
|
-
continuations.push(theme
|
|
845
|
+
continuations.push(fgCollapsed(theme, "muted", "…"));
|
|
839
846
|
const compact = [firstSummary, ...continuations]
|
|
840
847
|
.filter(hasVisibleContent)
|
|
841
|
-
.join(theme
|
|
848
|
+
.join(fgCollapsed(theme, "muted", " · "));
|
|
842
849
|
if (hasVisibleContent(compact)) return compact;
|
|
843
850
|
}
|
|
844
851
|
}
|
|
@@ -846,8 +853,8 @@ function renderedCallSummary(
|
|
|
846
853
|
|
|
847
854
|
const fallback = compactArgs(row.args);
|
|
848
855
|
return fallback
|
|
849
|
-
? theme
|
|
850
|
-
: theme
|
|
856
|
+
? fgCollapsed(theme, "toolOutput", fallback)
|
|
857
|
+
: fgCollapsed(theme, "muted", "(no arguments)");
|
|
851
858
|
}
|
|
852
859
|
|
|
853
860
|
function styledCallLabel(
|
|
@@ -857,15 +864,14 @@ function styledCallLabel(
|
|
|
857
864
|
): string {
|
|
858
865
|
const plain = sanitizeInline(stripAnsi(label)).trim();
|
|
859
866
|
const match = /^(\S+)(.*)$/s.exec(plain);
|
|
860
|
-
if (!match) return theme
|
|
867
|
+
if (!match) return fgCollapsed(theme, color, plain);
|
|
861
868
|
const rest = match[2] ?? "";
|
|
862
|
-
//
|
|
863
|
-
//
|
|
864
|
-
|
|
865
|
-
const restColor = color === "muted" ? "toolOutput" : color;
|
|
869
|
+
// The colon joins the bolded name to its content. Failed rows stay
|
|
870
|
+
// uniformly error-colored; settled rows use the collapsed mute — same
|
|
871
|
+
// color for the bolded name and the call content.
|
|
866
872
|
return (
|
|
867
|
-
theme
|
|
868
|
-
(rest ? theme
|
|
873
|
+
fgCollapsed(theme, color, match[1] ?? "", true) +
|
|
874
|
+
(rest ? fgCollapsed(theme, color, `:${rest}`) : "")
|
|
869
875
|
);
|
|
870
876
|
}
|
|
871
877
|
|
|
@@ -904,7 +910,7 @@ function collapsedOutcome(
|
|
|
904
910
|
}
|
|
905
911
|
if (row.getRenderShell?.() === "self") {
|
|
906
912
|
const editStat = renderedEditDiffStat(row);
|
|
907
|
-
if (editStat) return theme
|
|
913
|
+
if (editStat) return fgCollapsed(theme, "toolOutput", `→ ${editStat}`);
|
|
908
914
|
return renderedGenericOutcome(theme);
|
|
909
915
|
}
|
|
910
916
|
return renderedOutcome(row, theme);
|
|
@@ -917,18 +923,19 @@ function collapsedHeadline(
|
|
|
917
923
|
): string {
|
|
918
924
|
// Failed rows render entirely in error so the row reads as the one that
|
|
919
925
|
// failed, not just its outcome tail. Only the tool name is bold.
|
|
920
|
-
const
|
|
921
|
-
const marker = `${
|
|
922
|
-
|
|
926
|
+
const tone = rowHasFailed(row) ? "error" : "muted";
|
|
927
|
+
const marker = `${fgCollapsed(
|
|
928
|
+
theme,
|
|
929
|
+
tone,
|
|
923
930
|
row.toolName === "subagent" ? SUBAGENT_MARKER : GROUP_MARKER,
|
|
924
931
|
)} `;
|
|
925
932
|
const budget = Math.max(1, width - visibleWidth(marker));
|
|
926
|
-
const label = collapsedCallLabel(row, budget, theme,
|
|
933
|
+
const label = collapsedCallLabel(row, budget, theme, tone);
|
|
927
934
|
const outcome = collapsedOutcome(row, budget, theme);
|
|
928
935
|
// The truncation suffix inherits the row tone; pi-tui's truncation resets
|
|
929
936
|
// around a plain suffix, which would render it in the terminal default
|
|
930
937
|
// foreground instead of the row color.
|
|
931
|
-
const suffix = theme
|
|
938
|
+
const suffix = fgCollapsed(theme, tone, "…");
|
|
932
939
|
return (
|
|
933
940
|
marker +
|
|
934
941
|
(outcome
|
|
@@ -1099,20 +1106,25 @@ function renderSubagentPlan(
|
|
|
1099
1106
|
const failed = rowHasFailed(row);
|
|
1100
1107
|
const color = failed ? "error" : "muted";
|
|
1101
1108
|
const nameColor = failed ? "error" : "accent";
|
|
1102
|
-
const suffix = theme
|
|
1109
|
+
const suffix = fgCollapsed(theme, color, "…");
|
|
1103
1110
|
const displayNames = scrapeSubagentDisplayNames(row);
|
|
1104
1111
|
// Agent/profile/task values are model-supplied; sanitize them like every
|
|
1105
1112
|
// other collapsed-row text so control bytes cannot reach the terminal.
|
|
1106
1113
|
const displayOf = (agent: string) =>
|
|
1107
|
-
|
|
1114
|
+
fgCollapsed(
|
|
1115
|
+
theme,
|
|
1116
|
+
nameColor,
|
|
1117
|
+
displayNames.get(agent) ?? sanitizeInline(agent),
|
|
1118
|
+
);
|
|
1108
1119
|
const detailColor = failed ? "error" : "toolOutput";
|
|
1109
1120
|
const detailOf = (step: SubagentStep) =>
|
|
1110
|
-
|
|
1121
|
+
fgCollapsed(
|
|
1122
|
+
theme,
|
|
1111
1123
|
detailColor,
|
|
1112
1124
|
`${step.profile ? ` [${sanitizeInline(step.profile)}]` : ""} ${subagentStepPreview(step.task)}`,
|
|
1113
1125
|
);
|
|
1114
1126
|
|
|
1115
|
-
const marker = `${theme
|
|
1127
|
+
const marker = `${fgCollapsed(theme, color, SUBAGENT_MARKER, true)} `;
|
|
1116
1128
|
const budget = Math.max(1, width - visibleWidth(marker));
|
|
1117
1129
|
const progress = subagentProgressText(row);
|
|
1118
1130
|
const outcome = failed
|
|
@@ -1120,7 +1132,11 @@ function renderSubagentPlan(
|
|
|
1120
1132
|
: isLiveRow(row)
|
|
1121
1133
|
? theme.fg("warning", progress ? `→ ${progress}` : "…")
|
|
1122
1134
|
: row.result
|
|
1123
|
-
?
|
|
1135
|
+
? fgCollapsed(
|
|
1136
|
+
theme,
|
|
1137
|
+
"toolOutput",
|
|
1138
|
+
progress ? `→ ${progress}` : "→ done",
|
|
1139
|
+
)
|
|
1124
1140
|
: theme.fg("warning", "…");
|
|
1125
1141
|
const headline = (label: string) =>
|
|
1126
1142
|
marker +
|
|
@@ -1132,7 +1148,7 @@ function renderSubagentPlan(
|
|
|
1132
1148
|
const step = plan.steps[0]!;
|
|
1133
1149
|
return [
|
|
1134
1150
|
headline(
|
|
1135
|
-
theme
|
|
1151
|
+
fgCollapsed(theme, failed ? "error" : "toolTitle", "subagent", true) +
|
|
1136
1152
|
" " +
|
|
1137
1153
|
displayOf(step.agent) +
|
|
1138
1154
|
detailOf(step),
|
|
@@ -1146,22 +1162,24 @@ function renderSubagentPlan(
|
|
|
1146
1162
|
: `parallel (${plan.steps.length} tasks)`;
|
|
1147
1163
|
const lines = [
|
|
1148
1164
|
headline(
|
|
1149
|
-
theme
|
|
1165
|
+
fgCollapsed(theme, failed ? "error" : "toolTitle", "subagent", true) +
|
|
1150
1166
|
" " +
|
|
1151
|
-
theme
|
|
1152
|
-
theme
|
|
1167
|
+
fgCollapsed(theme, failed ? "error" : "toolOutput", kindLabel) +
|
|
1168
|
+
fgCollapsed(theme, failed ? "error" : "toolOutput", ` [${plan.scope}]`),
|
|
1153
1169
|
),
|
|
1154
1170
|
];
|
|
1155
1171
|
const shown = plan.steps.slice(0, 3);
|
|
1156
1172
|
for (let index = 0; index < shown.length; index++) {
|
|
1157
1173
|
const step = shown[index]!;
|
|
1158
1174
|
const number =
|
|
1159
|
-
plan.kind === "chain"
|
|
1175
|
+
plan.kind === "chain"
|
|
1176
|
+
? `${fgCollapsed(theme, color, `${index + 1}.`)} `
|
|
1177
|
+
: "";
|
|
1160
1178
|
lines.push(` ${number}${displayOf(step.agent)}${detailOf(step)}`);
|
|
1161
1179
|
}
|
|
1162
1180
|
if (plan.steps.length > shown.length) {
|
|
1163
1181
|
lines.push(
|
|
1164
|
-
` ${theme
|
|
1182
|
+
` ${fgCollapsed(theme, color, `... +${plan.steps.length - shown.length} more`)}`,
|
|
1165
1183
|
);
|
|
1166
1184
|
}
|
|
1167
1185
|
return lines;
|
|
@@ -1182,7 +1200,7 @@ function imageResultLines(
|
|
|
1182
1200
|
.join("\n");
|
|
1183
1201
|
lines.push(
|
|
1184
1202
|
...wrapTextWithAnsi(
|
|
1185
|
-
theme
|
|
1203
|
+
fgCollapsed(theme, "toolOutput", sanitized),
|
|
1186
1204
|
Math.max(1, width),
|
|
1187
1205
|
),
|
|
1188
1206
|
);
|
|
@@ -1238,7 +1256,7 @@ function groupedChildLabel(
|
|
|
1238
1256
|
Math.max(0, visibleWidth(label) - prefixWidth),
|
|
1239
1257
|
true,
|
|
1240
1258
|
);
|
|
1241
|
-
return child || theme
|
|
1259
|
+
return child || fgCollapsed(theme, color, "(no arguments)");
|
|
1242
1260
|
}
|
|
1243
1261
|
|
|
1244
1262
|
function renderGroupedCallLines(
|
|
@@ -1252,17 +1270,17 @@ function renderGroupedCallLines(
|
|
|
1252
1270
|
const toolName = row.toolName ?? "tool";
|
|
1253
1271
|
if (toolName !== previousToolName) {
|
|
1254
1272
|
lines.push(
|
|
1255
|
-
`${theme
|
|
1273
|
+
`${fgCollapsed(theme, "muted", GROUP_MARKER)} ${fgCollapsed(theme, "toolTitle", toolName, true)}`,
|
|
1256
1274
|
);
|
|
1257
1275
|
previousToolName = toolName;
|
|
1258
1276
|
}
|
|
1259
1277
|
|
|
1260
1278
|
const color = rowHasFailed(row) ? "error" : "muted";
|
|
1261
|
-
const prefix = ` ${theme
|
|
1279
|
+
const prefix = ` ${fgCollapsed(theme, color, "•")} `;
|
|
1262
1280
|
const budget = Math.max(1, width - visibleWidth(prefix));
|
|
1263
1281
|
const label = groupedChildLabel(row, budget, theme, color);
|
|
1264
1282
|
const outcome = collapsedOutcome(row, budget, theme);
|
|
1265
|
-
const suffix = theme
|
|
1283
|
+
const suffix = fgCollapsed(theme, color, "…");
|
|
1266
1284
|
lines.push(
|
|
1267
1285
|
prefix +
|
|
1268
1286
|
(outcome
|
|
@@ -1681,6 +1699,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
1681
1699
|
ctx.ui.setToolsExpanded(false);
|
|
1682
1700
|
});
|
|
1683
1701
|
|
|
1702
|
+
pi.on("session_start", (_event, ctx) => {
|
|
1703
|
+
if (patch) patch.theme = ctx.ui.theme;
|
|
1704
|
+
if (bashPatch) bashPatch.theme = ctx.ui.theme;
|
|
1705
|
+
ctx.ui.setToolsExpanded(false);
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1684
1708
|
pi.on("session_shutdown", () => {
|
|
1685
1709
|
if (released) return;
|
|
1686
1710
|
released = true;
|
package/src/muted.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Collapsed-row color policy.
|
|
2
|
+
//
|
|
3
|
+
// Collapsed blocks (tool calls, settled "+ Thought" labels, subagent rows)
|
|
4
|
+
// default to the theme's `syntaxComment` token — it ships with every Pi
|
|
5
|
+
// theme and reads as a muted tone — instead of the louder
|
|
6
|
+
// muted/toolTitle/toolOutput split. A theme can override each collapsed
|
|
7
|
+
// kind independently with a `collapsedToolCall` or `collapsedThinkingCall`
|
|
8
|
+
// color token.
|
|
9
|
+
//
|
|
10
|
+
// Failures always fall back to the historical tokens.
|
|
11
|
+
|
|
12
|
+
const TOOL_OVERRIDE_TOKEN = "collapsedToolCall";
|
|
13
|
+
const THINKING_OVERRIDE_TOKEN = "collapsedThinkingCall";
|
|
14
|
+
const DEFAULT_TOKEN = "syntaxComment";
|
|
15
|
+
|
|
16
|
+
type CollapsedTheme = {
|
|
17
|
+
fg(color: string, text: string): string;
|
|
18
|
+
bold?(text: string): string;
|
|
19
|
+
getFgAnsi?(color: string): string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Resolved per theme object (Pi's Theme throws on unknown tokens; themes
|
|
23
|
+
// without getFgAnsi have no overrides), alongside index.ts's other
|
|
24
|
+
// theme-identity-keyed caches.
|
|
25
|
+
const ansiCache = new WeakMap<object, Map<string, string | null>>();
|
|
26
|
+
|
|
27
|
+
function tokenAnsi(theme: CollapsedTheme, token: string): string | null {
|
|
28
|
+
let cache = ansiCache.get(theme);
|
|
29
|
+
if (!cache) {
|
|
30
|
+
cache = new Map();
|
|
31
|
+
ansiCache.set(theme, cache);
|
|
32
|
+
}
|
|
33
|
+
if (cache.has(token)) return cache.get(token)!;
|
|
34
|
+
let ansi: string | null = null;
|
|
35
|
+
try {
|
|
36
|
+
const resolved = theme.getFgAnsi?.(token);
|
|
37
|
+
if (typeof resolved === "string") ansi = resolved;
|
|
38
|
+
} catch {
|
|
39
|
+
ansi = null;
|
|
40
|
+
}
|
|
41
|
+
cache.set(token, ansi);
|
|
42
|
+
return ansi;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Override token when defined, else the theme's syntaxComment color, else
|
|
46
|
+
// null (caller falls back to the historical tokens).
|
|
47
|
+
function collapsedAnsi(
|
|
48
|
+
theme: CollapsedTheme,
|
|
49
|
+
overrideToken: string,
|
|
50
|
+
): string | null {
|
|
51
|
+
return tokenAnsi(theme, overrideToken) ?? tokenAnsi(theme, DEFAULT_TOKEN);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function collapsedToolAnsi(theme: CollapsedTheme): string | null {
|
|
55
|
+
return collapsedAnsi(theme, TOOL_OVERRIDE_TOKEN);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function collapsedThinkingAnsi(theme: CollapsedTheme): string | null {
|
|
59
|
+
return collapsedAnsi(theme, THINKING_OVERRIDE_TOKEN);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// One styling entry point for collapsed-row text. `color` is the historical
|
|
63
|
+
// token role ("muted", "toolOutput", "toolTitle", "accent", "error"); error
|
|
64
|
+
// keeps its semantic color in all paths.
|
|
65
|
+
export function fgCollapsed(
|
|
66
|
+
theme: CollapsedTheme,
|
|
67
|
+
color: string,
|
|
68
|
+
text: string,
|
|
69
|
+
bold = false,
|
|
70
|
+
): string {
|
|
71
|
+
if (color !== "error") {
|
|
72
|
+
const ansi = collapsedToolAnsi(theme);
|
|
73
|
+
if (ansi) {
|
|
74
|
+
return bold
|
|
75
|
+
? `${ansi}\x1b[1m${text}\x1b[22m\x1b[39m`
|
|
76
|
+
: `${ansi}${text}\x1b[39m`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return theme.fg(color, bold && theme.bold ? theme.bold(text) : text);
|
|
80
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { AssistantMessageComponent } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { infoVisibilityHidden } from "./info-visibility-state.ts";
|
|
4
|
+
import { collapsedThinkingAnsi } from "./muted.ts";
|
|
4
5
|
|
|
5
6
|
const THINKING_GROUPING_PATCHED = Symbol.for("kg.pi.thinkingGrouping.v2");
|
|
6
7
|
const PI_SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
@@ -44,15 +45,22 @@ type ThemeDetail = {
|
|
|
44
45
|
getColorMode?(): "truecolor" | "256color" | string;
|
|
45
46
|
};
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
// Live reference into the UI context: reading .theme per check keeps label
|
|
49
|
+
// styles current across mid-session theme switches, which fire no event.
|
|
50
|
+
let themeProvider: (() => ThemeDetail | undefined) | undefined;
|
|
48
51
|
let activeLevel: (() => string) | undefined;
|
|
52
|
+
// Theme object the current label styles were computed from.
|
|
53
|
+
let stylesTheme: ThemeDetail | undefined;
|
|
49
54
|
|
|
50
55
|
type LabelStyle = { prefix: string; suffix: string };
|
|
51
56
|
let liveLabelStyle: LabelStyle | undefined;
|
|
52
57
|
let settledLabelStyle: LabelStyle | undefined;
|
|
53
58
|
|
|
54
|
-
function styledWith(
|
|
55
|
-
|
|
59
|
+
function styledWith(
|
|
60
|
+
token: string,
|
|
61
|
+
theme: ThemeDetail | undefined = themeProvider?.(),
|
|
62
|
+
): LabelStyle | undefined {
|
|
63
|
+
const ansi = theme?.getFgAnsi?.(token);
|
|
56
64
|
if (!ansi) return undefined;
|
|
57
65
|
// The styled label replaces Pi's italicized Text node wholesale (see
|
|
58
66
|
// restyleHiddenThinkingLabel). The leading italic-off still matters: the
|
|
@@ -61,19 +69,36 @@ function styledWith(token: string): LabelStyle | undefined {
|
|
|
61
69
|
return { prefix: `\x1b[23m${ansi}`, suffix: "\x1b[39m" };
|
|
62
70
|
}
|
|
63
71
|
|
|
72
|
+
// The theme's collapsedThinkingCall override when defined, else its comment
|
|
73
|
+
// color, else the muted token.
|
|
74
|
+
function settledMutedStyle(
|
|
75
|
+
theme: ThemeDetail | undefined,
|
|
76
|
+
): LabelStyle | undefined {
|
|
77
|
+
const ansi = theme ? collapsedThinkingAnsi(theme) : null;
|
|
78
|
+
if (ansi) return { prefix: `\x1b[23m${ansi}`, suffix: "\x1b[39m" };
|
|
79
|
+
return styledWith("muted", theme);
|
|
80
|
+
}
|
|
64
81
|
function updateThoughtLabelStyle(): void {
|
|
65
82
|
liveLabelStyle = undefined;
|
|
66
83
|
settledLabelStyle = undefined;
|
|
84
|
+
const theme = themeProvider?.();
|
|
85
|
+
stylesTheme = theme;
|
|
67
86
|
const choice = thoughtLabelColorChoice();
|
|
68
|
-
if (choice === "inherit" || !
|
|
87
|
+
if (choice === "inherit" || !theme) return;
|
|
69
88
|
|
|
70
89
|
const token =
|
|
71
90
|
choice === "mdheading"
|
|
72
91
|
? "mdHeading"
|
|
73
92
|
: (LEVEL_TOKEN[activeLevel?.() ?? "off"] ?? "thinkingOff");
|
|
74
|
-
liveLabelStyle = styledWith(token);
|
|
93
|
+
liveLabelStyle = styledWith(token, theme);
|
|
75
94
|
settledLabelStyle =
|
|
76
|
-
choice === "mdheading" ? liveLabelStyle :
|
|
95
|
+
choice === "mdheading" ? liveLabelStyle : settledMutedStyle(theme);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Recompute label styles whenever the theme object has swapped (theme switch
|
|
99
|
+
// or /reload); those transitions fire no extension event.
|
|
100
|
+
function ensureLabelStyles(): void {
|
|
101
|
+
if (themeProvider?.() !== stylesTheme) updateThoughtLabelStyle();
|
|
77
102
|
}
|
|
78
103
|
|
|
79
104
|
// The settled label is the finalized "+ Thought" row; every other label is
|
|
@@ -83,6 +108,7 @@ function isSettledThoughtLabel(label: string): boolean {
|
|
|
83
108
|
}
|
|
84
109
|
|
|
85
110
|
export function visibleThoughtLabel(label: string): string {
|
|
111
|
+
ensureLabelStyles();
|
|
86
112
|
const style = isSettledThoughtLabel(label)
|
|
87
113
|
? settledLabelStyle
|
|
88
114
|
: liveLabelStyle;
|
|
@@ -418,7 +444,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
418
444
|
} catch {
|
|
419
445
|
// Best effort; stale rows would only linger until the process exits.
|
|
420
446
|
}
|
|
421
|
-
|
|
447
|
+
const uiCtx = ctx;
|
|
448
|
+
themeProvider = () => uiCtx.ui?.theme as unknown as ThemeDetail;
|
|
422
449
|
activeLevel = () => {
|
|
423
450
|
try {
|
|
424
451
|
return pi.getThinkingLevel();
|
|
@@ -433,8 +460,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
433
460
|
if (released) return;
|
|
434
461
|
released = true;
|
|
435
462
|
if (patch && !uninstallThinkingGroupingPatch(patch)) return;
|
|
436
|
-
|
|
463
|
+
themeProvider = undefined;
|
|
437
464
|
activeLevel = undefined;
|
|
465
|
+
stylesTheme = undefined;
|
|
438
466
|
liveLabelStyle = undefined;
|
|
439
467
|
settledLabelStyle = undefined;
|
|
440
468
|
});
|