@xzzpig/pi-starline 0.4.0 → 0.5.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/README.md +1 -1
- package/extensions/starline/config.ts +17 -10
- package/extensions/starline/mouse/api-consumer.ts +9 -2
- package/extensions/starline/mouse/capabilities.ts +4 -4
- package/extensions/starline/mouse/index.ts +5 -4
- package/extensions/starline/mouse/key-text.ts +64 -0
- package/extensions/starline/mouse/tool-box.ts +7 -66
- package/extensions/starline/settings-command.ts +8 -7
- package/extensions/starline/user-message.ts +71 -11
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -176,7 +176,7 @@ Settings live in `~/.pi/agent/starline.json`. The file is optional — anything
|
|
|
176
176
|
{
|
|
177
177
|
"footerStyle": "pill",
|
|
178
178
|
"colors": { "gitBranch": "bold purple" },
|
|
179
|
-
"mouse": { "
|
|
179
|
+
"mouse": { "clickToToggleExpandable": false }
|
|
180
180
|
}
|
|
181
181
|
```
|
|
182
182
|
|
|
@@ -109,8 +109,8 @@ export type MouseConfig = {
|
|
|
109
109
|
wheelRouting: boolean;
|
|
110
110
|
/** Show a "copied to clipboard" notice for a copy Starline itself performs. */
|
|
111
111
|
copyNotice: boolean;
|
|
112
|
-
/**
|
|
113
|
-
|
|
112
|
+
/** Click any collapsible box (a tool box, a custom message, an expandable text) to expand or collapse it. */
|
|
113
|
+
clickToToggleExpandable: boolean;
|
|
114
114
|
/** Copy transcript selections without the painted rails, rules and frames. */
|
|
115
115
|
transcriptCleanCopy: boolean;
|
|
116
116
|
};
|
|
@@ -119,7 +119,10 @@ const FIXED_EDITOR_KEY_MAP: Record<string, keyof MouseConfig> = {
|
|
|
119
119
|
enabled: "enabled",
|
|
120
120
|
mouseScroll: "wheelRouting",
|
|
121
121
|
copyNotice: "copyNotice",
|
|
122
|
-
|
|
122
|
+
// The 0.2.x-era name for click-to-toggle; Pi 0.86.0 owns the click itself
|
|
123
|
+
// for tool boxes, but the fork keeps the general collapsible-box toggle,
|
|
124
|
+
// so the old value is carried to the renamed key instead of being dropped.
|
|
125
|
+
clickToExpandTools: "clickToToggleExpandable",
|
|
123
126
|
};
|
|
124
127
|
|
|
125
128
|
/**
|
|
@@ -418,7 +421,7 @@ export const defaultConfig: PolishedTuiConfig = {
|
|
|
418
421
|
enabled: true,
|
|
419
422
|
wheelRouting: true,
|
|
420
423
|
copyNotice: true,
|
|
421
|
-
|
|
424
|
+
clickToToggleExpandable: true,
|
|
422
425
|
transcriptCleanCopy: true,
|
|
423
426
|
},
|
|
424
427
|
};
|
|
@@ -838,10 +841,14 @@ function normalizeMouseConfig(record: Record<string, unknown>): MouseConfig {
|
|
|
838
841
|
: defaultConfig.mouse.wheelRouting,
|
|
839
842
|
copyNotice:
|
|
840
843
|
typeof record.copyNotice === "boolean" ? record.copyNotice : defaultConfig.mouse.copyNotice,
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
844
|
+
clickToToggleExpandable:
|
|
845
|
+
// The new key wins outright; a config carrying the pre-rename
|
|
846
|
+
// mouse.clickToExpandTools value is honoured as a fallback.
|
|
847
|
+
typeof record.clickToToggleExpandable === "boolean"
|
|
848
|
+
? record.clickToToggleExpandable
|
|
849
|
+
: typeof record.clickToExpandTools === "boolean"
|
|
850
|
+
? record.clickToExpandTools
|
|
851
|
+
: defaultConfig.mouse.clickToToggleExpandable,
|
|
845
852
|
transcriptCleanCopy:
|
|
846
853
|
typeof record.transcriptCleanCopy === "boolean"
|
|
847
854
|
? record.transcriptCleanCopy
|
|
@@ -1306,8 +1313,8 @@ export function saveMousePatch(patch: Partial<MouseConfig>, path = configPath):
|
|
|
1306
1313
|
...(patch.enabled !== undefined ? { enabled: patch.enabled } : {}),
|
|
1307
1314
|
...(patch.wheelRouting !== undefined ? { wheelRouting: patch.wheelRouting } : {}),
|
|
1308
1315
|
...(patch.copyNotice !== undefined ? { copyNotice: patch.copyNotice } : {}),
|
|
1309
|
-
...(patch.
|
|
1310
|
-
? {
|
|
1316
|
+
...(patch.clickToToggleExpandable !== undefined
|
|
1317
|
+
? { clickToToggleExpandable: patch.clickToToggleExpandable }
|
|
1311
1318
|
: {}),
|
|
1312
1319
|
...(patch.transcriptCleanCopy !== undefined
|
|
1313
1320
|
? { transcriptCleanCopy: patch.transcriptCleanCopy }
|
|
@@ -22,10 +22,17 @@ import type { MouseEventsApi } from "@xzzpig/pi-mouse-events/api";
|
|
|
22
22
|
* MUST match `MOUSE_EVENTS_API_KEY` in `@xzzpig/pi-mouse-events/api` — the
|
|
23
23
|
* contract test above is what keeps that true.
|
|
24
24
|
*/
|
|
25
|
-
const MOUSE_EVENTS_API_KEY = Symbol.for("pi-mouse-events.api.
|
|
25
|
+
const MOUSE_EVENTS_API_KEY = Symbol.for("pi-mouse-events.api.v2");
|
|
26
26
|
|
|
27
27
|
export function getMouseEventsApi(): MouseEventsApi | undefined {
|
|
28
|
-
|
|
28
|
+
const candidate = (globalThis as Record<symbol, unknown>)[MOUSE_EVENTS_API_KEY] as
|
|
29
29
|
| MouseEventsApi
|
|
30
30
|
| undefined;
|
|
31
|
+
// The symbol key pins the contract name, not its shape: reject an object
|
|
32
|
+
// from a different contract major so callers never dereference a
|
|
33
|
+
// mismatched surface (the key is only bumped together with the version).
|
|
34
|
+
if (!candidate || candidate.version !== 2) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
return candidate;
|
|
31
38
|
}
|
|
@@ -29,7 +29,7 @@ export type MouseCapability = MouseReceiverCapabilities | "copySlot";
|
|
|
29
29
|
|
|
30
30
|
export type MouseFeature =
|
|
31
31
|
| "selectionHint"
|
|
32
|
-
| "
|
|
32
|
+
| "clickToToggleExpandable"
|
|
33
33
|
| "editorWheelScroll"
|
|
34
34
|
| "editorClickToCaret"
|
|
35
35
|
| "editorBufferCopy"
|
|
@@ -77,7 +77,7 @@ const REQUIREMENTS: Record<MouseFeature, readonly MouseCapability[]> = {
|
|
|
77
77
|
// whatever tool box happens to sit behind it — and `requestRender`, because
|
|
78
78
|
// consuming the press means Pi never reaches its own repaint for that
|
|
79
79
|
// event.
|
|
80
|
-
|
|
80
|
+
clickToToggleExpandable: ["hasOverlay", "requestRender"],
|
|
81
81
|
// Same shape as click-to-expand: the notch arrives through the handler
|
|
82
82
|
// slot, `hasOverlay` keeps a notch aimed at a dialog from scrolling the
|
|
83
83
|
// draft hidden behind it, and consuming the notch means `requestRender` is
|
|
@@ -113,7 +113,7 @@ const REQUIREMENTS: Record<MouseFeature, readonly MouseCapability[]> = {
|
|
|
113
113
|
],
|
|
114
114
|
};
|
|
115
115
|
|
|
116
|
-
function isCallable(receiver:
|
|
116
|
+
function isCallable<T extends object>(receiver: T, name: string): boolean {
|
|
117
117
|
try {
|
|
118
118
|
return typeof (receiver as Record<string, unknown>)[name] === "function";
|
|
119
119
|
} catch {
|
|
@@ -122,7 +122,7 @@ function isCallable(receiver: object, name: string): boolean {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
export function probeCapabilities(receiver:
|
|
125
|
+
export function probeCapabilities<T extends object>(receiver: T): ReadonlySet<MouseCapability> {
|
|
126
126
|
const found = new Set<MouseCapability>();
|
|
127
127
|
for (const capability of CAPABILITIES) {
|
|
128
128
|
if (capability === "copySlot") continue;
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* located and how the live editor is reached; the scroll itself is
|
|
22
22
|
* `editor-scroll.ts`.
|
|
23
23
|
*
|
|
24
|
-
* `
|
|
24
|
+
* `clickToToggleExpandable` → `addMouseHandler`
|
|
25
25
|
* - A left-button press anywhere on an expandable component records the
|
|
26
26
|
* candidate; when the button is released on the same cell with no motion in
|
|
27
27
|
* between — a plain click — that one component is toggled and the release
|
|
@@ -89,8 +89,9 @@ import { activeEditor, pointerOverEditor } from "./editor-mouse";
|
|
|
89
89
|
import { scrollEditorBy } from "./editor-scroll";
|
|
90
90
|
import { editorVisualRowCount } from "./editor-text-cursor";
|
|
91
91
|
import { type BoxLike, scrollContentLinesFor } from "./hit-test";
|
|
92
|
+
import { keyTextFor } from "./key-text";
|
|
92
93
|
import { externalEditorName, selectionHintText } from "./selection-state";
|
|
93
|
-
import { type ExpandTarget, expandKeyText, expandTargetAt
|
|
94
|
+
import { type ExpandTarget, expandKeyText, expandTargetAt } from "./tool-box";
|
|
94
95
|
import { cleanTranscriptRows } from "./transcript-copy";
|
|
95
96
|
|
|
96
97
|
/** Pi's interrupt chord — refused by the range-delete branch, never consumed. */
|
|
@@ -664,7 +665,7 @@ function expandClickTarget(
|
|
|
664
665
|
deps: InstallMouseDeps,
|
|
665
666
|
): ExpandTarget | undefined {
|
|
666
667
|
try {
|
|
667
|
-
if (!deps.getConfig().mouse.
|
|
668
|
+
if (!deps.getConfig().mouse.clickToToggleExpandable) return undefined;
|
|
668
669
|
// Pi resolves no scroll view while an overlay is up, so neither does this
|
|
669
670
|
// — a click on a dialog must not reach the transcript behind it.
|
|
670
671
|
if (receiver.hasOverlay?.()) return undefined;
|
|
@@ -806,7 +807,7 @@ export function installMouseFeaturesOn(
|
|
|
806
807
|
api.addMouseHandler(
|
|
807
808
|
({ event, tui }) => {
|
|
808
809
|
const receiver = bindReceiver(tui);
|
|
809
|
-
if (!receiver || !featureOn("
|
|
810
|
+
if (!receiver || !featureOn("clickToToggleExpandable")) {
|
|
810
811
|
expandPress = undefined;
|
|
811
812
|
return undefined;
|
|
812
813
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The keys spelled the way Pi's on-screen hints spell them.
|
|
3
|
+
*
|
|
4
|
+
* The primary source is `pi-coding-agent`'s own exported `keyText` — literally
|
|
5
|
+
* the function `keyHint` calls to build the hint text the UI shows, so there
|
|
6
|
+
* is no second formatting rule to drift (it is `keyText` that turns `alt`
|
|
7
|
+
* into `option` on macOS and joins alternatives with "/"), and no second
|
|
8
|
+
* keybinding registry to disagree.
|
|
9
|
+
*
|
|
10
|
+
* That last point is not theoretical. `getKeybindings()` is a *singleton* per
|
|
11
|
+
* copy of pi-tui, and a copy is per `node_modules` tree: in this repo
|
|
12
|
+
* `pi-coding-agent` nests its own pi-tui, so the registry Starline's direct
|
|
13
|
+
* import reaches is not the registry the hints are rendered from. Production
|
|
14
|
+
* resolves both to Pi's own copy, but reading the value through the same
|
|
15
|
+
* package that renders it makes the question moot instead of assumed.
|
|
16
|
+
*
|
|
17
|
+
* pi-tui's registry is kept as a fallback for the case where `keyText` yields
|
|
18
|
+
* nothing. Both routes return "" when the binding is unbound, and callers
|
|
19
|
+
* read "" as "show no key" rather than as a wildcard.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { keyText } from "@earendil-works/pi-coding-agent";
|
|
23
|
+
import { getKeybindings } from "@earendil-works/pi-tui";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* `keyText(keybinding)` with the registry fallback, shared by every feature
|
|
27
|
+
* that quotes a key on screen. See the module header for why the primary
|
|
28
|
+
* route is pi-coding-agent's own function rather than this repo's registry.
|
|
29
|
+
*/
|
|
30
|
+
export function keyTextFor(keybinding: string): string {
|
|
31
|
+
try {
|
|
32
|
+
const rendered = keyText(keybinding as never);
|
|
33
|
+
if (typeof rendered === "string" && rendered.length > 0) return rendered;
|
|
34
|
+
} catch {
|
|
35
|
+
// A Pi build that has moved this function is one where quoting the key
|
|
36
|
+
// simply shows nothing; it is never a reason to break a mouse feature.
|
|
37
|
+
}
|
|
38
|
+
return fallbackKeyText(keybinding);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* `keyText` re-derived from pi-tui's registry: keys joined with "/", `alt`
|
|
43
|
+
* shown as `option` on macOS, matching `formatKeyText` in
|
|
44
|
+
* `keybinding-hints.js`.
|
|
45
|
+
*/
|
|
46
|
+
function fallbackKeyText(keybinding: string): string {
|
|
47
|
+
try {
|
|
48
|
+
const bound: unknown = getKeybindings().getKeys(keybinding as never);
|
|
49
|
+
if (!Array.isArray(bound) || bound.length === 0) return "";
|
|
50
|
+
const keys = bound.filter((key): key is string => typeof key === "string");
|
|
51
|
+
return keys
|
|
52
|
+
.map((key: string) =>
|
|
53
|
+
key
|
|
54
|
+
.split("+")
|
|
55
|
+
.map((part) =>
|
|
56
|
+
process.platform === "darwin" && part.toLowerCase() === "alt" ? "option" : part,
|
|
57
|
+
)
|
|
58
|
+
.join("+"),
|
|
59
|
+
)
|
|
60
|
+
.join("/");
|
|
61
|
+
} catch {
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -45,12 +45,11 @@
|
|
|
45
45
|
* skill/branch/compaction summaries), so an expanded box used to have no
|
|
46
46
|
* clickable way back at all without `pi-toolbox`'s collapse anchor.
|
|
47
47
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* bowing out here is the only way its `onMouse` can mean what it says.
|
|
48
|
+
* Ownership is decided by resolution alone: a click is taken over iff it
|
|
49
|
+
* resolves to an expandable component (one with `setExpanded`). A component
|
|
50
|
+
* that handles the mouse itself — Pi's own tool boxes wrapped in a core
|
|
51
|
+
* `MouseRegion` included — is simply not expandable by this predicate, so it
|
|
52
|
+
* falls through to Pi's native handling instead of being fought over.
|
|
54
53
|
*
|
|
55
54
|
* ## Why resolution goes through the component tree
|
|
56
55
|
*
|
|
@@ -86,10 +85,10 @@
|
|
|
86
85
|
* same click a row above or below behaves normally.
|
|
87
86
|
*/
|
|
88
87
|
|
|
89
|
-
import {
|
|
90
|
-
import { getKeybindings, stripTerminalSequences } from "@earendil-works/pi-tui";
|
|
88
|
+
import { stripTerminalSequences } from "@earendil-works/pi-tui";
|
|
91
89
|
import { createComponentTree, isComponentLike, isExpandableComponent } from "./component-tree";
|
|
92
90
|
import { type BoxLike, boxesAt, scrollContentLinesFor, scrollContentOrigin } from "./hit-test";
|
|
91
|
+
import { keyTextFor } from "./key-text";
|
|
93
92
|
|
|
94
93
|
/** The slice of a Pi message component this module calls. */
|
|
95
94
|
export type ExpandableComponent = { setExpanded(expanded: boolean): void };
|
|
@@ -165,15 +164,6 @@ export function expandedStateOf(component: object): boolean | undefined {
|
|
|
165
164
|
return undefined;
|
|
166
165
|
}
|
|
167
166
|
|
|
168
|
-
/** Whether the component declares its own mouse handling (`onMouse`). */
|
|
169
|
-
function handlesMouse(component: unknown): boolean {
|
|
170
|
-
return (
|
|
171
|
-
typeof component === "object" &&
|
|
172
|
-
component !== null &&
|
|
173
|
-
typeof (component as { onMouse?: unknown }).onMouse === "function"
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
167
|
/**
|
|
178
168
|
* The keys spelled the way the hint on screen spells them.
|
|
179
169
|
*
|
|
@@ -200,47 +190,6 @@ export function expandKeyText(): string {
|
|
|
200
190
|
return keyTextFor(KEYBINDING);
|
|
201
191
|
}
|
|
202
192
|
|
|
203
|
-
/**
|
|
204
|
-
* `keyText(keybinding)` with the registry fallback, shared by every feature
|
|
205
|
-
* that quotes a key on screen. See `expandKeyText` for why the primary route
|
|
206
|
-
* is pi-coding-agent's own function rather than this repo's registry.
|
|
207
|
-
*/
|
|
208
|
-
export function keyTextFor(keybinding: string): string {
|
|
209
|
-
try {
|
|
210
|
-
const rendered = keyText(keybinding as never);
|
|
211
|
-
if (typeof rendered === "string" && rendered.length > 0) return rendered;
|
|
212
|
-
} catch {
|
|
213
|
-
// A Pi build that has moved this function is one where quoting the key
|
|
214
|
-
// simply shows nothing; it is never a reason to break a mouse feature.
|
|
215
|
-
}
|
|
216
|
-
return fallbackKeyText(keybinding);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* `keyText` re-derived from pi-tui's registry: keys joined with "/", `alt`
|
|
221
|
-
* shown as `option` on macOS, matching `formatKeyText` in
|
|
222
|
-
* `keybinding-hints.js`.
|
|
223
|
-
*/
|
|
224
|
-
function fallbackKeyText(keybinding: string): string {
|
|
225
|
-
try {
|
|
226
|
-
const bound: unknown = getKeybindings().getKeys(keybinding as never);
|
|
227
|
-
if (!Array.isArray(bound) || bound.length === 0) return "";
|
|
228
|
-
const keys = bound.filter((key): key is string => typeof key === "string");
|
|
229
|
-
return keys
|
|
230
|
-
.map((key: string) =>
|
|
231
|
-
key
|
|
232
|
-
.split("+")
|
|
233
|
-
.map((part) =>
|
|
234
|
-
process.platform === "darwin" && part.toLowerCase() === "alt" ? "option" : part,
|
|
235
|
-
)
|
|
236
|
-
.join("+"),
|
|
237
|
-
)
|
|
238
|
-
.join("/");
|
|
239
|
-
} catch {
|
|
240
|
-
return "";
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
193
|
/** The innermost scroll view whose box contains the screen cell. */
|
|
245
194
|
function scrollViewAt(root: BoxLike, x: number, y: number): unknown {
|
|
246
195
|
let found: unknown;
|
|
@@ -291,14 +240,6 @@ export function expandTargetAt(
|
|
|
291
240
|
const tree = createComponentTree(origin.component, origin.rect.width, lines);
|
|
292
241
|
const path = tree.pathAt(row);
|
|
293
242
|
|
|
294
|
-
// A component anywhere on the path that handles the mouse itself owns this
|
|
295
|
-
// click. The layout folds the transcript into one box, so `pi-mouse-events`
|
|
296
|
-
// dispatch can never deliver an event to it — the only way its `onMouse`
|
|
297
|
-
// can mean what it says is for this module to bow out.
|
|
298
|
-
for (const span of path) {
|
|
299
|
-
if (handlesMouse(span.component)) return undefined;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
243
|
for (let index = path.length - 1; index >= 0; index--) {
|
|
303
244
|
const span = path[index];
|
|
304
245
|
if (!isExpandableComponent(span.component)) continue;
|
|
@@ -469,11 +469,12 @@ function buildItems(
|
|
|
469
469
|
values: featureStateValues,
|
|
470
470
|
});
|
|
471
471
|
items.push({
|
|
472
|
-
id: "
|
|
473
|
-
label: "Click to
|
|
472
|
+
id: "mouseClickToToggleExpandable",
|
|
473
|
+
label: "Click to toggle expandable",
|
|
474
474
|
description:
|
|
475
|
-
"Click
|
|
476
|
-
|
|
475
|
+
"Click any collapsible component (tool boxes, bash boxes, skill/summary blocks, " +
|
|
476
|
+
"custom entries/messages) to expand or collapse just that one.",
|
|
477
|
+
currentValue: featureValue(config.mouse.clickToToggleExpandable),
|
|
477
478
|
values: featureStateValues,
|
|
478
479
|
});
|
|
479
480
|
}
|
|
@@ -831,11 +832,11 @@ export function registerStarlineSettingsCommand(pi: ExtensionAPI, deps: Settings
|
|
|
831
832
|
tui.requestRender();
|
|
832
833
|
return;
|
|
833
834
|
}
|
|
834
|
-
if (id === "
|
|
835
|
-
deps.setMouseConfig({
|
|
835
|
+
if (id === "mouseClickToToggleExpandable" && isFeatureState(newValue)) {
|
|
836
|
+
deps.setMouseConfig({ clickToToggleExpandable: newValue === "enabled" }, ctx);
|
|
836
837
|
settingsList.updateValue(id, newValue);
|
|
837
838
|
deps.requestRender();
|
|
838
|
-
ctx.ui.notify(`Click to
|
|
839
|
+
ctx.ui.notify(`Click to toggle expandable: ${newValue}`, "info");
|
|
839
840
|
tui.requestRender();
|
|
840
841
|
return;
|
|
841
842
|
}
|
|
@@ -30,6 +30,7 @@ type UserMessageRenderCache = {
|
|
|
30
30
|
width?: number;
|
|
31
31
|
theme?: Theme;
|
|
32
32
|
configKey?: string;
|
|
33
|
+
transformers?: readonly unknown[];
|
|
33
34
|
renderedLines?: string[];
|
|
34
35
|
};
|
|
35
36
|
|
|
@@ -69,6 +70,49 @@ function getCachedMarkdownText(instance: object): string | undefined {
|
|
|
69
70
|
return text;
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
type MarkdownTransformerFn = (
|
|
74
|
+
markdown: string,
|
|
75
|
+
context: {
|
|
76
|
+
messageType: "user" | "assistant" | "assistant-thinking";
|
|
77
|
+
isStreaming: boolean;
|
|
78
|
+
availableWidth: number;
|
|
79
|
+
},
|
|
80
|
+
) => unknown;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* pi 官方管道:UserMessageComponent 把扩展注册的 markdownTransformers 通过
|
|
84
|
+
* Markdown 的 transform 选项跑一遍(createMarkdownTransform)。我们重写了 render,
|
|
85
|
+
* 必须自己把这条管道接上,否则任何 registerMarkdownTransformer 的扩展
|
|
86
|
+
* (如 pi-ide-context 的选区引用行)在 starline 样式下都会被旁路。
|
|
87
|
+
*/
|
|
88
|
+
function getMarkdownTransformersRef(instance: object): readonly unknown[] | undefined {
|
|
89
|
+
if (!isRecord(instance)) return undefined;
|
|
90
|
+
const t = instance.markdownTransformers;
|
|
91
|
+
return Array.isArray(t) ? t : undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function applyMarkdownTransformers(
|
|
95
|
+
text: string,
|
|
96
|
+
availableWidth: number,
|
|
97
|
+
transformers: readonly unknown[],
|
|
98
|
+
): string {
|
|
99
|
+
let out = text;
|
|
100
|
+
for (const t of transformers) {
|
|
101
|
+
if (typeof t !== "function") continue;
|
|
102
|
+
try {
|
|
103
|
+
const r = (t as MarkdownTransformerFn)(out, {
|
|
104
|
+
messageType: "user",
|
|
105
|
+
isStreaming: false,
|
|
106
|
+
availableWidth,
|
|
107
|
+
});
|
|
108
|
+
if (typeof r === "string") out = r;
|
|
109
|
+
} catch {
|
|
110
|
+
// 与 pi 的 applyMarkdownTransformers 一致:单个 transformer 抛错不影响其他
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
|
|
72
116
|
function getUserMessageConfigKey(config: PolishedTuiConfig): string {
|
|
73
117
|
return [
|
|
74
118
|
config.features.copyFriendly ? "copy" : "chrome",
|
|
@@ -161,12 +205,14 @@ function renderStarlineUserMessage(
|
|
|
161
205
|
if (width <= 0) return [""];
|
|
162
206
|
|
|
163
207
|
const configKey = getUserMessageConfigKey(config);
|
|
208
|
+
const transformersRef = getMarkdownTransformersRef(instance);
|
|
164
209
|
const cached = userMessageRenderCache.get(instance);
|
|
165
210
|
if (
|
|
166
211
|
cached?.hasMarkdownText &&
|
|
167
212
|
cached.width === width &&
|
|
168
213
|
cached.theme === theme &&
|
|
169
214
|
cached.configKey === configKey &&
|
|
215
|
+
cached.transformers === transformersRef &&
|
|
170
216
|
cached.renderedLines
|
|
171
217
|
) {
|
|
172
218
|
return cached.renderedLines;
|
|
@@ -174,17 +220,30 @@ function renderStarlineUserMessage(
|
|
|
174
220
|
|
|
175
221
|
const railWidth = visibleWidth(renderPromptBoxRail(theme, config));
|
|
176
222
|
const contentWidth = Math.max(1, width - railWidth);
|
|
177
|
-
const renderer = new Markdown(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
223
|
+
const renderer = new Markdown(
|
|
224
|
+
text,
|
|
225
|
+
0,
|
|
226
|
+
0,
|
|
227
|
+
makeMarkdownTheme(theme),
|
|
228
|
+
{
|
|
229
|
+
color: (content) =>
|
|
230
|
+
theme && config.colors.userMessageText
|
|
231
|
+
? renderStyleForSource(
|
|
232
|
+
theme,
|
|
233
|
+
config.colorSources.userMessages,
|
|
234
|
+
config.colors.userMessageText,
|
|
235
|
+
content,
|
|
236
|
+
)
|
|
237
|
+
: themeFg(theme, "userMessageText", content),
|
|
238
|
+
},
|
|
239
|
+
// 接上官方 transformer 管道;Markdown 每次 render 都会按当前宽度重跑
|
|
240
|
+
{
|
|
241
|
+
transform: transformersRef?.length
|
|
242
|
+
? (markdown, availableWidth) =>
|
|
243
|
+
applyMarkdownTransformers(markdown, availableWidth, transformersRef)
|
|
244
|
+
: undefined,
|
|
245
|
+
},
|
|
246
|
+
);
|
|
188
247
|
const body = renderer.render(contentWidth);
|
|
189
248
|
const contentLines = body.length > 0 ? body : [""];
|
|
190
249
|
const border = theme
|
|
@@ -212,6 +271,7 @@ function renderStarlineUserMessage(
|
|
|
212
271
|
width,
|
|
213
272
|
theme,
|
|
214
273
|
configKey,
|
|
274
|
+
transformers: transformersRef,
|
|
215
275
|
renderedLines: lines,
|
|
216
276
|
});
|
|
217
277
|
return lines;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xzzpig/pi-starline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "A Starship-inspired statusline and Opencode-style TUI for Pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"extensions"
|
|
24
24
|
],
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@earendil-works/pi-ai": ">=0.
|
|
27
|
-
"@earendil-works/pi-coding-agent": ">=0.
|
|
28
|
-
"@earendil-works/pi-tui": ">=0.
|
|
29
|
-
"@xzzpig/pi-mouse-events": ">=0.
|
|
26
|
+
"@earendil-works/pi-ai": ">=0.86.0 <0.88",
|
|
27
|
+
"@earendil-works/pi-coding-agent": ">=0.86.0 <0.88",
|
|
28
|
+
"@earendil-works/pi-tui": ">=0.86.0 <0.88",
|
|
29
|
+
"@xzzpig/pi-mouse-events": ">=0.2.0 <3"
|
|
30
30
|
},
|
|
31
31
|
"pi": {
|
|
32
32
|
"extensions": [
|
|
@@ -39,13 +39,13 @@
|
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@biomejs/biome": "^2.5.0",
|
|
42
|
-
"@earendil-works/pi-ai": "^0.
|
|
43
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
44
|
-
"@earendil-works/pi-tui": "^0.
|
|
42
|
+
"@earendil-works/pi-ai": "^0.87.1",
|
|
43
|
+
"@earendil-works/pi-coding-agent": "^0.87.1",
|
|
44
|
+
"@earendil-works/pi-tui": "^0.87.1",
|
|
45
45
|
"@types/node": "^26.0.0",
|
|
46
46
|
"typescript": "^7.0.2",
|
|
47
47
|
"vitest": "^4.1.8",
|
|
48
|
-
"@xzzpig/pi-mouse-events": "0.
|
|
48
|
+
"@xzzpig/pi-mouse-events": "0.2.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependenciesMeta": {
|
|
51
51
|
"@xzzpig/pi-mouse-events": {
|