@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.
Files changed (45) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +223 -0
  3. package/extensions/starline/config.ts +1317 -0
  4. package/extensions/starline/editor-cursor.ts +55 -0
  5. package/extensions/starline/editor-factory-marker.ts +49 -0
  6. package/extensions/starline/editor-metadata-format.ts +259 -0
  7. package/extensions/starline/extension-status.ts +88 -0
  8. package/extensions/starline/footer-format.ts +279 -0
  9. package/extensions/starline/footer.ts +744 -0
  10. package/extensions/starline/format.ts +473 -0
  11. package/extensions/starline/git.ts +467 -0
  12. package/extensions/starline/icons.ts +294 -0
  13. package/extensions/starline/index.ts +651 -0
  14. package/extensions/starline/live-context.ts +75 -0
  15. package/extensions/starline/mouse/capabilities.ts +207 -0
  16. package/extensions/starline/mouse/component-tree.ts +230 -0
  17. package/extensions/starline/mouse/editor-caret.ts +498 -0
  18. package/extensions/starline/mouse/editor-hit-test.ts +133 -0
  19. package/extensions/starline/mouse/editor-mouse.ts +142 -0
  20. package/extensions/starline/mouse/editor-scroll.ts +119 -0
  21. package/extensions/starline/mouse/editor-text-cursor.ts +209 -0
  22. package/extensions/starline/mouse/editor-text-edit.ts +90 -0
  23. package/extensions/starline/mouse/hint.ts +14 -0
  24. package/extensions/starline/mouse/hit-test.ts +144 -0
  25. package/extensions/starline/mouse/index.ts +1119 -0
  26. package/extensions/starline/mouse/selection-state.ts +68 -0
  27. package/extensions/starline/mouse/tool-box.ts +290 -0
  28. package/extensions/starline/mouse/transcript-copy.ts +166 -0
  29. package/extensions/starline/mouse/word-select.ts +145 -0
  30. package/extensions/starline/package-version.ts +650 -0
  31. package/extensions/starline/paste-collapse.ts +312 -0
  32. package/extensions/starline/pill-config.ts +122 -0
  33. package/extensions/starline/pill.ts +295 -0
  34. package/extensions/starline/project-refresh.ts +104 -0
  35. package/extensions/starline/project-state.ts +66 -0
  36. package/extensions/starline/prototype-patch-registry.ts +136 -0
  37. package/extensions/starline/runtime.ts +841 -0
  38. package/extensions/starline/selector-border.ts +77 -0
  39. package/extensions/starline/session-lifecycle.ts +60 -0
  40. package/extensions/starline/settings-command.ts +925 -0
  41. package/extensions/starline/state.ts +65 -0
  42. package/extensions/starline/style.ts +528 -0
  43. package/extensions/starline/ui.ts +611 -0
  44. package/extensions/starline/user-message.ts +267 -0
  45. 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
+ }