@pi-kaush/pi-tool-call-markers 0.2.8 → 0.2.10
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 +29 -0
- package/README.md +27 -4
- package/package.json +1 -1
- package/src/bash-block.ts +303 -0
- package/src/index.ts +229 -65
- package/src/info-visibility-state.ts +22 -0
- package/src/info-visibility.ts +74 -0
- package/src/thinking-block-merger.ts +111 -136
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Add `/toggle-info`: hide tool calls and thinking entirely, or restore them
|
|
6
|
+
collapsed. Execution rows (including user-run `!` blocks) are lifted out of
|
|
7
|
+
the render pass via the shared chat-container hook; thinking blocks are
|
|
8
|
+
stripped before Pi builds the label/expanded block and replayed on restore.
|
|
9
|
+
Every session starts visible, and Ctrl+O/Ctrl+T behavior is unchanged when
|
|
10
|
+
visible. The toggle state lives on globalThis because Pi loads each package
|
|
11
|
+
entrypoint through jiti with module caching disabled — module-level state
|
|
12
|
+
would otherwise be duplicated between entrypoints.
|
|
13
|
+
- Adopt Pi's native content-color split in collapsed rows: tool names ride
|
|
14
|
+
`toolTitle` and call content/outcomes ride `toolOutput` (structural
|
|
15
|
+
chrome stays muted; failed rows stay uniformly error).
|
|
16
|
+
- Tint the collapsed "+ Thought" label with the session's active
|
|
17
|
+
thinking-level token (thinkingOff…thinkingMax), re-tinting on
|
|
18
|
+
thinking_level_select; the expanded thinking block is untouched. The
|
|
19
|
+
PI_TOOL_CALL_MARKERS_THOUGHT_COLOR values are now `level` (default),
|
|
20
|
+
`mdheading`, and `inherit`; the midpoint `gray` variant and its RGB
|
|
21
|
+
color math are gone.
|
|
22
|
+
- Paint user-run `!` bash block surfaces with the theme's
|
|
23
|
+
`userMessageBg` token instead of a hardcoded hex, matching
|
|
24
|
+
pi-content-layout's prompt surfaces under any theme.
|
|
25
|
+
- Show subagent progress in the collapsed plan headline: while running, the tail reads `→ N turns · provider/model` in the warning tone from streamed result details; settled calls keep the same `→ N turns · provider/model` in muted instead of a bare `→ done` (turns aggregate across tasks, model only when all tasks agree).
|
|
26
|
+
- Source the settled "+ Thought" label color from the theme's mdHeading
|
|
27
|
+
token (orange in cobalt2, amber in Pi's stock themes) instead of a
|
|
28
|
+
hardcoded RGB. The PI_TOOL_CALL_MARKERS_THOUGHT_COLOR experiment values
|
|
29
|
+
are now `mdheading` (default), `gray`, and `inherit`.
|
|
30
|
+
- Fix: preserve the visible text of hyperlink-wrapped paths in collapsed rows. Pi renders `read` call paths as OSC 8 hyperlinks (`ESC]8;;url ESC\ <path> ESC]8;; ESC\`); the previous greedy OSC strip consumed the path text along with the sequences, collapsing `% read: README.md:1-400` to `% read: :1-400`. OSC payloads now end at their first BEL/ST terminator, so `read`/`write`/`ls` rows show their filenames again.
|
|
31
|
+
- Show settled `edit` changes inline: the call line gains a `+added/-removed` outcome stat, and the display diff from `result.details.diff` renders as a bounded block underneath (`+` lines in the added tone, `-` in the removed tone, context muted, `...` for folded regions; capped at 12 lines with a `+N more` tail). `Ctrl+O` still returns Pi's full native diff.
|
|
32
|
+
- Render subagent plans with a `&` marker instead of `%`, so delegated calls read differently from ordinary tool rows (both the parsed plan shape and the generic fallback).
|
|
33
|
+
- Render user-run `!` bash blocks (BashExecutionComponent) with the railed prompt shell previously owned by `pi-content-layout` — execution display now lives entirely in this package, while `pi-content-layout` keeps only the transcript surface (message insets, status rows, editor, user prompts). The bash-block inset mirrors that package's message inset.
|
|
5
34
|
- Render every collapsed tool call as a `% tool: call → outcome` line with the tool name bolded — one marker per call, no bullets, no blank lines between sections; grouped blocks share only their leading blank row (supersedes the nested sub-heading layout).
|
|
6
35
|
- Render subagent calls as an unboxed plan — `% subagent` heading with chain/parallel counts and numbered steps as they execute, agent names in accent with emojis scraped from the native plan component (args fallback) — replacing the accent-rail card; failed subagents follow the full-red failure tone.
|
|
7
36
|
- Strip display sequences and control bytes (notably `\r` from progress writers like git) from collapsed-row text so command output cannot return the cursor to column 0 and overwrite the row.
|
package/README.md
CHANGED
|
@@ -21,21 +21,44 @@ Collapsed tool rows use semantic theme colors with no gear, background fill, box
|
|
|
21
21
|
- **Quiet-turn grouping.** Sequential calls can join across an assistant row with no visible prose or thinking. Visible assistant content remains a boundary.
|
|
22
22
|
- **MCP and self-rendered tools.** Their stable call labels, compact arguments, pending state, success, and first error line use the same collapsed shell. Native self-rendered details return when expanded.
|
|
23
23
|
- **Images remain visible.** Image fallback text and terminal image components render below the corresponding marker with the same inset.
|
|
24
|
+
- **User-run `!` bash blocks.** Execution display is owned here: user-typed commands trade Pi's green rules for the railed prompt shell (dark surface, status-colored rail) that submitted prompts use.
|
|
24
25
|
- **Native expansion remains authoritative.** `Ctrl+O` restores Pi's full individual tool rendering, including complete results, custom renderers, and error details.
|
|
25
26
|
|
|
26
27
|
## Subagent plans
|
|
27
28
|
|
|
28
|
-
A recognized `subagent` call renders as an unboxed plan in the shared tool aesthetic:
|
|
29
|
+
A recognized `subagent` call renders as an unboxed plan in the shared tool aesthetic, marked with `&` instead of the ordinary `%` tool marker:
|
|
29
30
|
|
|
30
31
|
```text
|
|
31
|
-
|
|
32
|
+
& subagent chain (3 steps) [repo-review]
|
|
32
33
|
1. 🐝 bee [workhorse] Challenge the compatibility conclusion…
|
|
33
34
|
2. 🐝 bee …
|
|
34
35
|
```
|
|
35
36
|
|
|
36
|
-
Single calls stay on one
|
|
37
|
+
Single calls stay on one `& subagent <agent> [profile] <task preview>` row; chain calls get a heading with the kind, count, and scope followed by numbered steps (parallel tasks list without numbers). Agent display names (emoji + name) are scraped from the native plan component with an args fallback, and render in `accent`; everything else stays muted, and failed subagents go full red. Subagents never join ordinary tool groups.
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
While a subagent runs, the plan headline's tail shows live progress from the streamed result details — `→ 1 turn · provider/model` in the warning tone — and a settled call keeps the same `→ N turns · provider/model` summary in muted (turns aggregate across tasks; the model shows only when every task used the same one).
|
|
40
|
+
|
|
41
|
+
Malformed, ambiguous, future, or too-narrow shapes fall back to the generic `& subagent …` collapsed row rather than dropping information, and `Ctrl+O` still exposes the native subagent renderer.
|
|
42
|
+
|
|
43
|
+
## Edit diffs
|
|
44
|
+
|
|
45
|
+
Settled `edit` calls keep their change visible without expanding: the call line gains a `+added/-removed` outcome stat, and the hunk renders as a bounded diff block underneath (`+` lines in the added tone, `-` in the removed tone, context muted, folded regions as `...`, capped at 12 lines with a count tail). The full native diff — line numbers, intra-line word highlights — still returns with `Ctrl+O`.
|
|
46
|
+
|
|
47
|
+
Files and paths in collapsed rows are preserved as displayed by Pi: hyperlink-wrapped paths (Pi wraps `read` call paths in OSC 8 hyperlinks) keep their visible text when sanitized for one-line rows.
|
|
48
|
+
|
|
49
|
+
## Scope boundary
|
|
50
|
+
|
|
51
|
+
This package owns every **execution row** in the transcript: collapsed tool calls, tool grouping, subagent plans, thinking labels, and user-run `!` bash blocks (reshaped into the railed prompt shell). Transcript _surface_ layout — message insets, system-text and status-rule alignment, the editor surface, and the submitted-prompt shell for user messages — belongs to `@pi-kaush/pi-content-layout`, which never renders tool rows.
|
|
52
|
+
|
|
53
|
+
One shared visual contract crosses the line: `!` blocks align with message text, so their inset mirrors `pi-content-layout`'s message `contentInset` (see `src/bash-block.ts`). Change indentation in both packages together.
|
|
54
|
+
|
|
55
|
+
## Hiding info entirely: /toggle-info
|
|
56
|
+
|
|
57
|
+
The default transcript keeps tool calls and thinking collapsed, and Ctrl+O /
|
|
58
|
+
Ctrl+T expand them as usual. When you want prose only, `/toggle-info` hides
|
|
59
|
+
tool calls (including user-run `!` blocks) and thinking completely; running it
|
|
60
|
+
again restores the collapsed view. The toggle is per-session: every session
|
|
61
|
+
starts visible.
|
|
39
62
|
|
|
40
63
|
## Bundled thinking-block extension
|
|
41
64
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { BashExecutionComponent } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
|
|
4
|
+
type ThemeLike = {
|
|
5
|
+
bold(text: string): string;
|
|
6
|
+
fg(color: string, text: string): string;
|
|
7
|
+
getBgAnsi?(color: string): string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const BASH_BLOCK_PATCHED = Symbol.for("kg.pi.bashBlock.v1");
|
|
11
|
+
|
|
12
|
+
// The prompt surface paints with the theme's userMessageBg token, matching
|
|
13
|
+
// pi-content-layout's surfaces. userMessageBg is a required pi theme token;
|
|
14
|
+
// the legacy hex remains only as a fallback for theme lookalikes without
|
|
15
|
+
// getBgAnsi.
|
|
16
|
+
const PROMPT_SURFACE_BG_FALLBACK = "\x1b[48;2;7;19;18m"; // #071312
|
|
17
|
+
|
|
18
|
+
function promptSurfaceBg(theme: ThemeLike): string {
|
|
19
|
+
try {
|
|
20
|
+
return theme.getBgAnsi?.("userMessageBg") ?? PROMPT_SURFACE_BG_FALLBACK;
|
|
21
|
+
} catch {
|
|
22
|
+
return PROMPT_SURFACE_BG_FALLBACK;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const PROMPT_RAIL = "▎";
|
|
26
|
+
|
|
27
|
+
// User-run bash blocks align with the message inset pi-content-layout
|
|
28
|
+
// applies to transcript lines. Keep this in sync with that package's
|
|
29
|
+
// contentInset (2 columns outside the rail for any width above 4); the
|
|
30
|
+
// conversation surface has one shared visual contract even though the two
|
|
31
|
+
// packages otherwise own disjoint components.
|
|
32
|
+
function bashBlockInset(width: number): number {
|
|
33
|
+
return width > BASH_BLOCK_INSET * 2 ? BASH_BLOCK_INSET : 0;
|
|
34
|
+
}
|
|
35
|
+
const BASH_BLOCK_INSET = 2;
|
|
36
|
+
|
|
37
|
+
const SGR_PATTERN = /\x1b\[([0-9;]*)m/g;
|
|
38
|
+
const BACKGROUND_PATTERN =
|
|
39
|
+
/\x1b\[(?:4[0-8]|10[0-7]|48;5;\d{1,3}|48;2;\d{1,3};\d{1,3};\d{1,3})m/g;
|
|
40
|
+
const DISPLAY_ANSI_PATTERN = /\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
|
|
41
|
+
|
|
42
|
+
function semanticControlEnd(text: string, offset: number): number | undefined {
|
|
43
|
+
if (text[offset] !== "\x1b") return undefined;
|
|
44
|
+
const kind = text[offset + 1];
|
|
45
|
+
if (kind !== "]" && kind !== "_" && kind !== "P" && kind !== "^") {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
for (let index = offset + 2; index < text.length; index++) {
|
|
49
|
+
if (text[index] === "\x07") return index + 1;
|
|
50
|
+
if (text[index] === "\x1b" && text[index + 1] === "\\") {
|
|
51
|
+
return index + 2;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function splitLeadingSemanticControls(line: string): {
|
|
58
|
+
controls: string;
|
|
59
|
+
content: string;
|
|
60
|
+
} {
|
|
61
|
+
let offset = 0;
|
|
62
|
+
while (offset < line.length) {
|
|
63
|
+
const end = semanticControlEnd(line, offset);
|
|
64
|
+
if (end === undefined) break;
|
|
65
|
+
offset = end;
|
|
66
|
+
}
|
|
67
|
+
return { controls: line.slice(0, offset), content: line.slice(offset) };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function fitLine(line: string, width: number): string {
|
|
71
|
+
if (width <= 0) return "";
|
|
72
|
+
const clipped = truncateToWidth(line, width, "");
|
|
73
|
+
return clipped + " ".repeat(Math.max(0, width - visibleWidth(clipped)));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function reapplyBackgroundAfterReset(
|
|
77
|
+
text: string,
|
|
78
|
+
backgroundAnsi: string,
|
|
79
|
+
): string {
|
|
80
|
+
return text.replace(SGR_PATTERN, (sequence, rawParameters: string) => {
|
|
81
|
+
const parameters =
|
|
82
|
+
rawParameters === "" ? [0] : rawParameters.split(";").map(Number);
|
|
83
|
+
return parameters.includes(0) || parameters.includes(49)
|
|
84
|
+
? `${sequence}${backgroundAnsi}`
|
|
85
|
+
: sequence;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function paintBackground(
|
|
90
|
+
line: string,
|
|
91
|
+
width: number,
|
|
92
|
+
backgroundAnsi: string,
|
|
93
|
+
): string {
|
|
94
|
+
const fitted = fitLine(line, width);
|
|
95
|
+
// Mirrors Theme.bg: background, content, then a background-only reset.
|
|
96
|
+
return `${backgroundAnsi}${reapplyBackgroundAfterReset(fitted, backgroundAnsi)}\x1b[49m`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function stripDisplayAnsi(text: string): string {
|
|
100
|
+
return text.replace(DISPLAY_ANSI_PATTERN, "");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function replaceBackground(text: string, backgroundAnsi: string): string {
|
|
104
|
+
return text.replace(BACKGROUND_PATTERN, backgroundAnsi);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Renders lines inside a railed, dark-surfaced block (the submitted-prompt
|
|
108
|
+
// shell): optional leading semantic controls stay outside the inset, the rail
|
|
109
|
+
// is recolored, and content backgrounds are normalized to the prompt surface.
|
|
110
|
+
function renderRailedBlockLines(
|
|
111
|
+
lines: string[],
|
|
112
|
+
width: number,
|
|
113
|
+
theme: ThemeLike,
|
|
114
|
+
inset: number,
|
|
115
|
+
railColor: string,
|
|
116
|
+
): string[] {
|
|
117
|
+
const blockWidth = width - inset * 2;
|
|
118
|
+
if (inset === 0 || blockWidth <= visibleWidth(PROMPT_RAIL)) {
|
|
119
|
+
return lines.map((line) => fitLine(line, width));
|
|
120
|
+
}
|
|
121
|
+
const bodyWidth = blockWidth - visibleWidth(PROMPT_RAIL);
|
|
122
|
+
const margin = " ".repeat(inset);
|
|
123
|
+
const surfaceBg = promptSurfaceBg(theme);
|
|
124
|
+
return lines.map((line) => {
|
|
125
|
+
const { controls, content } = splitLeadingSemanticControls(line);
|
|
126
|
+
const recolored = replaceBackground(content, surfaceBg);
|
|
127
|
+
const rail = theme.fg(railColor, PROMPT_RAIL);
|
|
128
|
+
// One extra leading space inside the body, so submitted text sits two
|
|
129
|
+
// columns right of the rail instead of one.
|
|
130
|
+
const body = paintBackground(
|
|
131
|
+
` ${fitLine(recolored, Math.max(1, bodyWidth - 1))}`,
|
|
132
|
+
bodyWidth,
|
|
133
|
+
surfaceBg,
|
|
134
|
+
);
|
|
135
|
+
return `${controls}${margin}${rail}${body}${margin}`;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const RULE_LINE_RE = /^─+$/;
|
|
140
|
+
|
|
141
|
+
function isRuleLine(line: string | undefined): line is string {
|
|
142
|
+
return line !== undefined && RULE_LINE_RE.test(stripDisplayAnsi(line).trim());
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// `!!` commands draw their rules dim instead of bashMode green, but the
|
|
146
|
+
// component does not store that flag, so sniff the top rule's color to keep
|
|
147
|
+
// the rail consistent with the framing it replaces.
|
|
148
|
+
function sniffsDimRule(rule: string, theme: ThemeLike): boolean {
|
|
149
|
+
const prefixOf = (colored: string) => colored.slice(0, colored.indexOf("─"));
|
|
150
|
+
const dimPrefix = prefixOf(theme.fg("dim", "─"));
|
|
151
|
+
return (
|
|
152
|
+
dimPrefix.length > 0 &&
|
|
153
|
+
dimPrefix !== prefixOf(theme.fg("bashMode", "─")) &&
|
|
154
|
+
rule.startsWith(dimPrefix)
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Rail tone for a user bash block: failures read red and cancellations
|
|
159
|
+
// yellow, matching the block's status text; anything else keeps the green
|
|
160
|
+
// the rules used (dim for `!!` commands).
|
|
161
|
+
function bashBlockRailColor(
|
|
162
|
+
status: string | undefined,
|
|
163
|
+
topRule: string | undefined,
|
|
164
|
+
theme: ThemeLike,
|
|
165
|
+
): string {
|
|
166
|
+
if (status === "error") return "error";
|
|
167
|
+
if (status === "cancelled") return "warning";
|
|
168
|
+
if (topRule !== undefined && sniffsDimRule(topRule, theme)) return "dim";
|
|
169
|
+
return "bashMode";
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Reshapes BashExecutionComponent output into the submitted-prompt shell: the
|
|
173
|
+
// leading spacer stays outside the block, the top/bottom rules are dropped,
|
|
174
|
+
// and the remaining lines get one blank padded row at each end plus the rail
|
|
175
|
+
// and dark surface.
|
|
176
|
+
function renderBashBlockLines(
|
|
177
|
+
lines: string[],
|
|
178
|
+
width: number,
|
|
179
|
+
theme: ThemeLike,
|
|
180
|
+
status: string | undefined,
|
|
181
|
+
inset = bashBlockInset(width),
|
|
182
|
+
): string[] {
|
|
183
|
+
const [spacer, ...rest] = lines;
|
|
184
|
+
const topRule = isRuleLine(rest[0]) ? rest[0] : undefined;
|
|
185
|
+
const inner = topRule !== undefined ? rest.slice(1) : rest.slice();
|
|
186
|
+
if (isRuleLine(inner[inner.length - 1])) inner.pop();
|
|
187
|
+
const railColor = bashBlockRailColor(status, topRule, theme);
|
|
188
|
+
const block = renderRailedBlockLines(
|
|
189
|
+
["", ...inner, ""],
|
|
190
|
+
width,
|
|
191
|
+
theme,
|
|
192
|
+
inset,
|
|
193
|
+
railColor,
|
|
194
|
+
);
|
|
195
|
+
return [fitLine(spacer ?? "", width), ...block];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
type BashRow = { status?: unknown };
|
|
199
|
+
|
|
200
|
+
type BashBlockPatchState = {
|
|
201
|
+
owners: number;
|
|
202
|
+
theme?: ThemeLike;
|
|
203
|
+
cache: WeakMap<object, { width: number; src: string[]; lines: string[] }>;
|
|
204
|
+
originalRender: (width: number) => string[];
|
|
205
|
+
patchedRender?: (width: number) => string[];
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// User-typed `!` commands are execution rows, so their presentation belongs
|
|
209
|
+
// here with every other collapsed execution display; pi-content-layout stops
|
|
210
|
+
// at the message surface. Pi's BashExecutionComponent already draws the
|
|
211
|
+
// command, rules, and status; this patch reshapes that output into the
|
|
212
|
+
// railed prompt shell and recolors the rail from the exit outcome.
|
|
213
|
+
// TODO: Replace prototype patching with a public Pi rendering API when available.
|
|
214
|
+
function installBashBlockPatch(): BashBlockPatchState | undefined {
|
|
215
|
+
try {
|
|
216
|
+
const proto = BashExecutionComponent?.prototype as unknown as BashRow & {
|
|
217
|
+
[BASH_BLOCK_PATCHED]?: BashBlockPatchState;
|
|
218
|
+
render?: (width: number) => string[];
|
|
219
|
+
};
|
|
220
|
+
if (!proto || typeof proto.render !== "function") return undefined;
|
|
221
|
+
|
|
222
|
+
const existing = proto[BASH_BLOCK_PATCHED];
|
|
223
|
+
if (existing) {
|
|
224
|
+
existing.owners++;
|
|
225
|
+
return existing;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const state: BashBlockPatchState = {
|
|
229
|
+
owners: 1,
|
|
230
|
+
cache: new WeakMap(),
|
|
231
|
+
originalRender: proto.render,
|
|
232
|
+
};
|
|
233
|
+
const patchedRender = function renderWithBashBlockShell(
|
|
234
|
+
this: BashRow,
|
|
235
|
+
width: number,
|
|
236
|
+
): string[] {
|
|
237
|
+
const theme = state.theme;
|
|
238
|
+
if (!theme) return state.originalRender.call(this, width);
|
|
239
|
+
const inset = bashBlockInset(width);
|
|
240
|
+
const bodyWidth = width - inset * 2 - visibleWidth(PROMPT_RAIL);
|
|
241
|
+
if (inset === 0 || bodyWidth < 8) {
|
|
242
|
+
return state.originalRender.call(this, width);
|
|
243
|
+
}
|
|
244
|
+
const lines = state.originalRender.call(this, Math.max(1, bodyWidth));
|
|
245
|
+
const entry = state.cache.get(this);
|
|
246
|
+
if (
|
|
247
|
+
entry &&
|
|
248
|
+
entry.width === width &&
|
|
249
|
+
entry.src.length === lines.length &&
|
|
250
|
+
entry.src.every((line, index) => line === lines[index])
|
|
251
|
+
) {
|
|
252
|
+
return entry.lines;
|
|
253
|
+
}
|
|
254
|
+
const status = this.status;
|
|
255
|
+
const decorated = renderBashBlockLines(
|
|
256
|
+
lines,
|
|
257
|
+
width,
|
|
258
|
+
theme,
|
|
259
|
+
typeof status === "string" ? status : undefined,
|
|
260
|
+
inset,
|
|
261
|
+
);
|
|
262
|
+
state.cache.set(this, { width, src: lines, lines: decorated });
|
|
263
|
+
return decorated;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
state.patchedRender = patchedRender;
|
|
268
|
+
proto.render = patchedRender;
|
|
269
|
+
Object.defineProperty(proto, BASH_BLOCK_PATCHED, {
|
|
270
|
+
configurable: true,
|
|
271
|
+
value: state,
|
|
272
|
+
});
|
|
273
|
+
} catch {
|
|
274
|
+
proto.render = state.originalRender;
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
return state;
|
|
278
|
+
} catch {
|
|
279
|
+
// Pi internals can change across versions; fail silently.
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function uninstallBashBlockPatch(state: BashBlockPatchState | undefined): void {
|
|
285
|
+
if (!state || state.owners <= 0) return;
|
|
286
|
+
state.owners--;
|
|
287
|
+
if (state.owners > 0) return;
|
|
288
|
+
const proto = BashExecutionComponent?.prototype as unknown as BashRow & {
|
|
289
|
+
[BASH_BLOCK_PATCHED]?: BashBlockPatchState;
|
|
290
|
+
render?: (width: number) => string[];
|
|
291
|
+
};
|
|
292
|
+
if (
|
|
293
|
+
proto[BASH_BLOCK_PATCHED] !== state ||
|
|
294
|
+
proto.render !== state.patchedRender
|
|
295
|
+
) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
proto.render = state.originalRender;
|
|
299
|
+
delete proto[BASH_BLOCK_PATCHED];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export { PROMPT_RAIL, installBashBlockPatch, uninstallBashBlockPatch };
|
|
303
|
+
export type { BashBlockPatchState };
|