@xzzpig/pi-starline 0.1.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/LICENSE +22 -0
- package/README.md +223 -0
- package/extensions/starline/config.ts +1317 -0
- package/extensions/starline/editor-cursor.ts +55 -0
- package/extensions/starline/editor-factory-marker.ts +49 -0
- package/extensions/starline/editor-metadata-format.ts +259 -0
- package/extensions/starline/extension-status.ts +88 -0
- package/extensions/starline/footer-format.ts +279 -0
- package/extensions/starline/footer.ts +744 -0
- package/extensions/starline/format.ts +473 -0
- package/extensions/starline/git.ts +467 -0
- package/extensions/starline/icons.ts +294 -0
- package/extensions/starline/index.ts +651 -0
- package/extensions/starline/live-context.ts +75 -0
- package/extensions/starline/mouse/capabilities.ts +207 -0
- package/extensions/starline/mouse/component-tree.ts +230 -0
- package/extensions/starline/mouse/editor-caret.ts +498 -0
- package/extensions/starline/mouse/editor-hit-test.ts +133 -0
- package/extensions/starline/mouse/editor-mouse.ts +142 -0
- package/extensions/starline/mouse/editor-scroll.ts +119 -0
- package/extensions/starline/mouse/editor-text-cursor.ts +209 -0
- package/extensions/starline/mouse/editor-text-edit.ts +90 -0
- package/extensions/starline/mouse/hint.ts +14 -0
- package/extensions/starline/mouse/hit-test.ts +144 -0
- package/extensions/starline/mouse/index.ts +1119 -0
- package/extensions/starline/mouse/selection-state.ts +68 -0
- package/extensions/starline/mouse/tool-box.ts +290 -0
- package/extensions/starline/mouse/transcript-copy.ts +166 -0
- package/extensions/starline/mouse/word-select.ts +145 -0
- package/extensions/starline/package-version.ts +650 -0
- package/extensions/starline/paste-collapse.ts +312 -0
- package/extensions/starline/pill-config.ts +122 -0
- package/extensions/starline/pill.ts +295 -0
- package/extensions/starline/project-refresh.ts +104 -0
- package/extensions/starline/project-state.ts +66 -0
- package/extensions/starline/prototype-patch-registry.ts +136 -0
- package/extensions/starline/runtime.ts +841 -0
- package/extensions/starline/selector-border.ts +77 -0
- package/extensions/starline/session-lifecycle.ts +60 -0
- package/extensions/starline/settings-command.ts +925 -0
- package/extensions/starline/state.ts +65 -0
- package/extensions/starline/style.ts +528 -0
- package/extensions/starline/ui.ts +611 -0
- package/extensions/starline/user-message.ts +267 -0
- package/package.json +60 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { type Theme, type ThemeColor, UserMessageComponent } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
Markdown,
|
|
4
|
+
type MarkdownTheme,
|
|
5
|
+
truncateToWidth,
|
|
6
|
+
visibleWidth,
|
|
7
|
+
} from "@earendil-works/pi-tui";
|
|
8
|
+
import type { PolishedTuiConfig } from "./config";
|
|
9
|
+
import { installPrototypePatch } from "./prototype-patch-registry";
|
|
10
|
+
import {
|
|
11
|
+
EDITOR_ACCENT_FALLBACK,
|
|
12
|
+
EDITOR_BORDER_FALLBACK,
|
|
13
|
+
renderStyleForSource,
|
|
14
|
+
renderStyleForSourceOrFallback,
|
|
15
|
+
} from "./style";
|
|
16
|
+
|
|
17
|
+
const OSC133_ZONE_START = "\x1b]133;A\x07";
|
|
18
|
+
const OSC133_ZONE_END = "\x1b]133;B\x07";
|
|
19
|
+
const OSC133_ZONE_FINAL = "\x1b]133;C\x07";
|
|
20
|
+
|
|
21
|
+
type PatchableUserMessagePrototype = {
|
|
22
|
+
children?: unknown[];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type Cleanup = () => void;
|
|
26
|
+
|
|
27
|
+
type UserMessageRenderCache = {
|
|
28
|
+
hasMarkdownText: boolean;
|
|
29
|
+
text?: string;
|
|
30
|
+
width?: number;
|
|
31
|
+
theme?: Theme;
|
|
32
|
+
configKey?: string;
|
|
33
|
+
renderedLines?: string[];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const userMessageRenderCache = new WeakMap<object, UserMessageRenderCache>();
|
|
37
|
+
|
|
38
|
+
function isObject(value: unknown): value is object {
|
|
39
|
+
return (typeof value === "object" && value !== null) || typeof value === "function";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
43
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function findMarkdownText(value: unknown): string | undefined {
|
|
47
|
+
if (!isRecord(value)) return undefined;
|
|
48
|
+
if (typeof value.text === "string") return value.text;
|
|
49
|
+
|
|
50
|
+
const children = value.children;
|
|
51
|
+
if (!Array.isArray(children)) return undefined;
|
|
52
|
+
|
|
53
|
+
for (const child of children) {
|
|
54
|
+
const text = findMarkdownText(child);
|
|
55
|
+
if (text !== undefined) return text;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getCachedMarkdownText(instance: object): string | undefined {
|
|
62
|
+
const cached = userMessageRenderCache.get(instance);
|
|
63
|
+
if (cached?.hasMarkdownText) return cached.text;
|
|
64
|
+
|
|
65
|
+
const text = findMarkdownText(instance);
|
|
66
|
+
if (text !== undefined) {
|
|
67
|
+
userMessageRenderCache.set(instance, { ...cached, hasMarkdownText: true, text });
|
|
68
|
+
}
|
|
69
|
+
return text;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getUserMessageConfigKey(config: PolishedTuiConfig): string {
|
|
73
|
+
return [
|
|
74
|
+
config.features.copyFriendly ? "copy" : "chrome",
|
|
75
|
+
config.colorSources.userMessages,
|
|
76
|
+
config.colors.editorAccent ?? "",
|
|
77
|
+
config.colors.editorBorder ?? "",
|
|
78
|
+
config.colors.userMessageBorder ?? "",
|
|
79
|
+
config.colors.userMessageText ?? "",
|
|
80
|
+
String(config.userMessagePaddingY),
|
|
81
|
+
config.icons.rail,
|
|
82
|
+
].join("\0");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function themeFg(theme: Theme | undefined, color: ThemeColor, text: string): string {
|
|
86
|
+
if (!theme) return text;
|
|
87
|
+
try {
|
|
88
|
+
return theme.fg(color, text);
|
|
89
|
+
} catch {
|
|
90
|
+
return text;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function makeMarkdownTheme(theme: Theme | undefined): MarkdownTheme {
|
|
95
|
+
return {
|
|
96
|
+
heading: (text) => themeFg(theme, "mdHeading", text),
|
|
97
|
+
link: (text) => themeFg(theme, "mdLink", text),
|
|
98
|
+
linkUrl: (text) => themeFg(theme, "mdLinkUrl", text),
|
|
99
|
+
code: (text) => themeFg(theme, "mdCode", text),
|
|
100
|
+
codeBlock: (text) => themeFg(theme, "mdCodeBlock", text),
|
|
101
|
+
codeBlockBorder: (text) => themeFg(theme, "mdCodeBlockBorder", text),
|
|
102
|
+
quote: (text) => themeFg(theme, "mdQuote", text),
|
|
103
|
+
quoteBorder: (text) => themeFg(theme, "mdQuoteBorder", text),
|
|
104
|
+
hr: (text) => themeFg(theme, "mdHr", text),
|
|
105
|
+
listBullet: (text) => themeFg(theme, "mdListBullet", text),
|
|
106
|
+
bold: (text) => (theme ? theme.bold(text) : text),
|
|
107
|
+
italic: (text) => (theme ? theme.italic(text) : text),
|
|
108
|
+
underline: (text) => (theme ? theme.underline(text) : text),
|
|
109
|
+
strikethrough: (text) => (theme ? theme.strikethrough(text) : text),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function fillLine(content: string, width: number): string {
|
|
114
|
+
const truncated = truncateToWidth(content, Math.max(0, width), "");
|
|
115
|
+
const pad = " ".repeat(Math.max(0, width - visibleWidth(truncated)));
|
|
116
|
+
return `${truncated}${pad}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function renderPromptBoxRail(theme: Theme | undefined, config: PolishedTuiConfig): string {
|
|
120
|
+
if (config.features.copyFriendly) return "";
|
|
121
|
+
const railGlyph = config.icons.rail;
|
|
122
|
+
|
|
123
|
+
return `${
|
|
124
|
+
theme
|
|
125
|
+
? renderStyleForSourceOrFallback(
|
|
126
|
+
theme,
|
|
127
|
+
config.colorSources.userMessages,
|
|
128
|
+
config.colors.editorAccent,
|
|
129
|
+
EDITOR_ACCENT_FALLBACK,
|
|
130
|
+
railGlyph,
|
|
131
|
+
)
|
|
132
|
+
: railGlyph
|
|
133
|
+
} `;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function renderPromptBoxLine(
|
|
137
|
+
line: string,
|
|
138
|
+
width: number,
|
|
139
|
+
theme: Theme | undefined,
|
|
140
|
+
config: PolishedTuiConfig,
|
|
141
|
+
): string {
|
|
142
|
+
if (width <= 0) return "";
|
|
143
|
+
const rail = renderPromptBoxRail(theme, config);
|
|
144
|
+
const contentWidth = Math.max(0, width - visibleWidth(rail));
|
|
145
|
+
const content = config.features.copyFriendly
|
|
146
|
+
? truncateToWidth(line, contentWidth, "")
|
|
147
|
+
: fillLine(line, contentWidth);
|
|
148
|
+
return truncateToWidth(`${rail}${content}`, width, "");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function renderStarlineUserMessage(
|
|
152
|
+
instance: PatchableUserMessagePrototype,
|
|
153
|
+
width: number,
|
|
154
|
+
theme: Theme | undefined,
|
|
155
|
+
config: PolishedTuiConfig,
|
|
156
|
+
): string[] | undefined {
|
|
157
|
+
if (!isRecord(instance)) return undefined;
|
|
158
|
+
|
|
159
|
+
const text = getCachedMarkdownText(instance);
|
|
160
|
+
if (text === undefined) return undefined;
|
|
161
|
+
if (width <= 0) return [""];
|
|
162
|
+
|
|
163
|
+
const configKey = getUserMessageConfigKey(config);
|
|
164
|
+
const cached = userMessageRenderCache.get(instance);
|
|
165
|
+
if (
|
|
166
|
+
cached?.hasMarkdownText &&
|
|
167
|
+
cached.width === width &&
|
|
168
|
+
cached.theme === theme &&
|
|
169
|
+
cached.configKey === configKey &&
|
|
170
|
+
cached.renderedLines
|
|
171
|
+
) {
|
|
172
|
+
return cached.renderedLines;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const railWidth = visibleWidth(renderPromptBoxRail(theme, config));
|
|
176
|
+
const contentWidth = Math.max(1, width - railWidth);
|
|
177
|
+
const renderer = new Markdown(text, 0, 0, makeMarkdownTheme(theme), {
|
|
178
|
+
color: (content) =>
|
|
179
|
+
theme && config.colors.userMessageText
|
|
180
|
+
? renderStyleForSource(
|
|
181
|
+
theme,
|
|
182
|
+
config.colorSources.userMessages,
|
|
183
|
+
config.colors.userMessageText,
|
|
184
|
+
content,
|
|
185
|
+
)
|
|
186
|
+
: themeFg(theme, "userMessageText", content),
|
|
187
|
+
});
|
|
188
|
+
const body = renderer.render(contentWidth);
|
|
189
|
+
const contentLines = body.length > 0 ? body : [""];
|
|
190
|
+
const border = theme
|
|
191
|
+
? renderStyleForSourceOrFallback(
|
|
192
|
+
theme,
|
|
193
|
+
config.colorSources.userMessages,
|
|
194
|
+
config.colors.userMessageBorder ?? config.colors.editorBorder,
|
|
195
|
+
EDITOR_BORDER_FALLBACK,
|
|
196
|
+
"─".repeat(width),
|
|
197
|
+
)
|
|
198
|
+
: "─".repeat(width);
|
|
199
|
+
const padRows =
|
|
200
|
+
config.userMessagePaddingY > 0 ? [renderPromptBoxLine("", width, theme, config)] : [];
|
|
201
|
+
const lines = [
|
|
202
|
+
truncateToWidth(border, width, ""),
|
|
203
|
+
...padRows,
|
|
204
|
+
...contentLines.map((line) => renderPromptBoxLine(line, width, theme, config)),
|
|
205
|
+
...padRows,
|
|
206
|
+
truncateToWidth(border, width, ""),
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
userMessageRenderCache.set(instance, {
|
|
210
|
+
hasMarkdownText: true,
|
|
211
|
+
text,
|
|
212
|
+
width,
|
|
213
|
+
theme,
|
|
214
|
+
configKey,
|
|
215
|
+
renderedLines: lines,
|
|
216
|
+
});
|
|
217
|
+
return lines;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function withPromptZoneMarkers(lines: string[]): string[] {
|
|
221
|
+
const markedLines = [...lines];
|
|
222
|
+
markedLines[0] = OSC133_ZONE_START + markedLines[0];
|
|
223
|
+
markedLines[markedLines.length - 1] =
|
|
224
|
+
OSC133_ZONE_END + OSC133_ZONE_FINAL + markedLines[markedLines.length - 1];
|
|
225
|
+
return markedLines;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function installUserMessageStyle(
|
|
229
|
+
getTheme: () => Theme | undefined,
|
|
230
|
+
getConfig: () => PolishedTuiConfig,
|
|
231
|
+
): Cleanup {
|
|
232
|
+
const prototype = UserMessageComponent.prototype;
|
|
233
|
+
const cleanupInvalidate = installPrototypePatch(
|
|
234
|
+
prototype,
|
|
235
|
+
"invalidate",
|
|
236
|
+
"user-message-invalidate",
|
|
237
|
+
({ predecessor, receiver, args }) => {
|
|
238
|
+
if (isObject(receiver)) userMessageRenderCache.delete(receiver);
|
|
239
|
+
return Reflect.apply(predecessor, receiver, args);
|
|
240
|
+
},
|
|
241
|
+
);
|
|
242
|
+
const cleanupRender = installPrototypePatch(
|
|
243
|
+
prototype,
|
|
244
|
+
"render",
|
|
245
|
+
"user-message-render",
|
|
246
|
+
({ predecessor, receiver, args }) => {
|
|
247
|
+
const width = args[0];
|
|
248
|
+
if (typeof width !== "number") return Reflect.apply(predecessor, receiver, args);
|
|
249
|
+
const lines = renderStarlineUserMessage(
|
|
250
|
+
receiver as PatchableUserMessagePrototype,
|
|
251
|
+
width,
|
|
252
|
+
getTheme(),
|
|
253
|
+
getConfig(),
|
|
254
|
+
);
|
|
255
|
+
if (!lines) return Reflect.apply(predecessor, receiver, args);
|
|
256
|
+
if (lines.length === 0) return lines;
|
|
257
|
+
return withPromptZoneMarkers(lines);
|
|
258
|
+
},
|
|
259
|
+
);
|
|
260
|
+
let cleaned = false;
|
|
261
|
+
return () => {
|
|
262
|
+
if (cleaned) return;
|
|
263
|
+
cleaned = true;
|
|
264
|
+
cleanupRender();
|
|
265
|
+
cleanupInvalidate();
|
|
266
|
+
};
|
|
267
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xzzpig/pi-starline",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A Starship-inspired statusline and Opencode-style TUI for Pi.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Andy8647/pi-starline.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/Andy8647/pi-starline#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/Andy8647/pi-starline/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"pi-package",
|
|
17
|
+
"pi-extension",
|
|
18
|
+
"pi-theme",
|
|
19
|
+
"starship",
|
|
20
|
+
"tui"
|
|
21
|
+
],
|
|
22
|
+
"files": [
|
|
23
|
+
"extensions"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"lint": "biome check .",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"pi:dev": "${PI_BIN:-/opt/homebrew/bin/pi} --no-extensions -e ./extensions/starline/index.ts",
|
|
30
|
+
"pi:install-local": "${PI_BIN:-/opt/homebrew/bin/pi} install ./ -l",
|
|
31
|
+
"fmt": "biome format --write .",
|
|
32
|
+
"fmt:check": "biome format --check .",
|
|
33
|
+
"verify": "npm run lint && npm run typecheck && npm run test",
|
|
34
|
+
"pack:check": "npm pack --dry-run",
|
|
35
|
+
"prepublishOnly": "npm run verify && npm run pack:check"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@earendil-works/pi-ai": ">=0.80.3",
|
|
39
|
+
"@earendil-works/pi-coding-agent": ">=0.80.3",
|
|
40
|
+
"@earendil-works/pi-tui": ">=0.80.3"
|
|
41
|
+
},
|
|
42
|
+
"pi": {
|
|
43
|
+
"extensions": [
|
|
44
|
+
"./extensions"
|
|
45
|
+
],
|
|
46
|
+
"image": "https://raw.githubusercontent.com/Andy8647/pi-starline/main/assets/starline.png"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@biomejs/biome": "^2.5.0",
|
|
53
|
+
"@earendil-works/pi-ai": "^0.82.1",
|
|
54
|
+
"@earendil-works/pi-coding-agent": "^0.84.1",
|
|
55
|
+
"@earendil-works/pi-tui": "^0.84.1",
|
|
56
|
+
"@types/node": "^26.0.0",
|
|
57
|
+
"typescript": "^7.0.2",
|
|
58
|
+
"vitest": "^4.1.8"
|
|
59
|
+
}
|
|
60
|
+
}
|