osdy-pi 0.1.8 → 1.2.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 +209 -161
- package/bin/osdy-pi.mjs +127 -0
- package/extensions/osdy-pi/account-profiles.test.ts +517 -0
- package/extensions/osdy-pi/account-profiles.ts +581 -0
- package/extensions/osdy-pi/animation.ts +47 -5
- package/extensions/osdy-pi/constants.ts +282 -103
- package/extensions/osdy-pi/diff-panel.ts +78 -24
- package/extensions/osdy-pi/editor-settings.test.ts +163 -0
- package/extensions/osdy-pi/editor-settings.ts +112 -0
- package/extensions/osdy-pi/metrics.ts +8 -135
- package/extensions/osdy-pi/plugin-events.ts +26 -0
- package/extensions/osdy-pi/profile-label.ts +29 -0
- package/extensions/osdy-pi/runtime-helpers.test.ts +26 -0
- package/extensions/osdy-pi/runtime-helpers.ts +87 -44
- package/extensions/osdy-pi/runtime.test.ts +62 -0
- package/extensions/osdy-pi/runtime.ts +256 -64
- package/extensions/osdy-pi/types.ts +33 -10
- package/extensions/osdy-pi/ui.test.ts +51 -0
- package/extensions/osdy-pi/ui.ts +298 -114
- package/extensions/osdy-pi/utils.ts +43 -1
- package/extensions/osdy-pi/working-tree.ts +25 -6
- package/package.json +22 -5
- package/scripts/osdy-pi-account-profiles.mjs +501 -0
- package/themes/osdy-pi-catppuccin-frappe.json +94 -0
- package/themes/osdy-pi-catppuccin-latte.json +94 -0
- package/themes/osdy-pi-catppuccin-macchiato.json +94 -0
- package/themes/osdy-pi-catppuccin-mocha.json +94 -0
- package/themes/osdy-pi-dark.json +0 -6
- package/themes/osdy-pi-dracula.json +83 -0
- package/themes/osdy-pi-kanagawa-dragon.json +91 -0
- package/themes/osdy-pi-kanagawa-lotus.json +92 -0
- package/themes/osdy-pi-kanagawa-wave.json +91 -0
- package/themes/osdy-pi-lucent-orange.json +80 -0
- package/themes/osdy-pi-matrix.json +83 -0
- package/themes/osdy-pi-new.json +91 -0
- package/themes/osdy-pi-sexy.json +84 -0
- package/themes/osdy-pi-tokyo-night.json +83 -0
- package/themes/osdy-pi-light.json +0 -84
- package/themes/osdy-pi-simple.json +0 -83
package/extensions/osdy-pi/ui.ts
CHANGED
|
@@ -20,66 +20,329 @@ import {
|
|
|
20
20
|
import { fitBorder } from "./border.js";
|
|
21
21
|
import {
|
|
22
22
|
ANIMATION_INTERVAL_MS,
|
|
23
|
+
COMPACT_HEADER_HIDDEN_COLUMNS,
|
|
23
24
|
headerWidth,
|
|
24
|
-
HEADER_FALLBACK,
|
|
25
25
|
HEADER_VARIANTS,
|
|
26
26
|
INTRO_ANIMATION_FRAMES,
|
|
27
27
|
MASCOT_GAP,
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
mascotWidthForRows,
|
|
29
|
+
scaleHeader,
|
|
30
|
+
scaleMascot,
|
|
31
|
+
STACKED_CONTENT_MAX_ROWS,
|
|
32
|
+
STACKED_HEADER_MAX_ROWS,
|
|
33
|
+
STACKED_HEADER_MIN_ROWS,
|
|
34
|
+
type ScaledHeaderArt,
|
|
30
35
|
WORKING_SPINNER_FRAMES,
|
|
31
36
|
} from "./constants.js";
|
|
32
37
|
import { formatPath } from "./format.js";
|
|
38
|
+
import { modelLabel, usageLabel } from "./metrics.js";
|
|
33
39
|
import {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
} from "./metrics.js";
|
|
39
|
-
import type { OsdyState, SimpleTheme, WorkingWidgetState } from "./types.js";
|
|
40
|
+
formatModelMetadata,
|
|
41
|
+
resolveActiveProfileLabel,
|
|
42
|
+
resolveEditorTitleLabel,
|
|
43
|
+
} from "./profile-label.js";
|
|
40
44
|
import {
|
|
45
|
+
shouldShowFooterMetadata,
|
|
46
|
+
type HeaderVariant,
|
|
47
|
+
type OsdyState,
|
|
48
|
+
type SimpleTheme,
|
|
49
|
+
type WorkingWidgetState,
|
|
50
|
+
} from "./types.js";
|
|
51
|
+
import {
|
|
52
|
+
compactMascotWidthBudget,
|
|
41
53
|
composeSideBySide,
|
|
42
54
|
fitCenterVisible,
|
|
43
55
|
internalLineTarget,
|
|
44
56
|
sanitizeStatusText,
|
|
45
57
|
} from "./utils.js";
|
|
46
58
|
|
|
59
|
+
const THINKING_THEME_TOKENS = {
|
|
60
|
+
off: "thinkingOff",
|
|
61
|
+
minimal: "thinkingMinimal",
|
|
62
|
+
low: "thinkingLow",
|
|
63
|
+
medium: "thinkingMedium",
|
|
64
|
+
high: "thinkingHigh",
|
|
65
|
+
xhigh: "thinkingXhigh",
|
|
66
|
+
max: "thinkingXhigh",
|
|
67
|
+
} satisfies Record<ReturnType<ExtensionAPI["getThinkingLevel"]>, string>;
|
|
68
|
+
|
|
69
|
+
const PI_LENS_STATUS_KEY = "pi-lens-lsp";
|
|
70
|
+
|
|
71
|
+
function stackedHeaderRowBudget(terminalRows: number): number {
|
|
72
|
+
const contentRows = Math.min(terminalRows, STACKED_CONTENT_MAX_ROWS);
|
|
73
|
+
return Math.min(STACKED_HEADER_MAX_ROWS, Math.floor(contentRows / 3));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
type HeaderLayout = {
|
|
77
|
+
canUseFullHeader: boolean;
|
|
78
|
+
hasMascot: boolean;
|
|
79
|
+
compactHeader: ScaledHeaderArt | undefined;
|
|
80
|
+
fullHeaderWidth: number;
|
|
81
|
+
mascotRowBudget: number;
|
|
82
|
+
mascotWidthBudget: number;
|
|
83
|
+
renderWidth: number;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
type HeaderConfig = (typeof HEADER_VARIANTS)[HeaderVariant];
|
|
87
|
+
|
|
88
|
+
function calculateHeaderLayout(
|
|
89
|
+
variantName: HeaderVariant,
|
|
90
|
+
variant: HeaderConfig,
|
|
91
|
+
width: number,
|
|
92
|
+
terminalColumns: number,
|
|
93
|
+
terminalRows: number,
|
|
94
|
+
): HeaderLayout {
|
|
95
|
+
const renderWidth = Math.max(1, width);
|
|
96
|
+
const fullHeaderWidth = headerWidth(variantName);
|
|
97
|
+
const mascotArt = {
|
|
98
|
+
mascot: variant.mascot ?? [],
|
|
99
|
+
toneMap: variant.mascotMap ?? [],
|
|
100
|
+
};
|
|
101
|
+
const hasMascot = mascotArt.mascot.length > 0 && mascotArt.toneMap.length > 0;
|
|
102
|
+
const heightLimitedMascotWidth = hasMascot
|
|
103
|
+
? mascotWidthForRows(mascotArt, terminalRows)
|
|
104
|
+
: 0;
|
|
105
|
+
const canUseFullHeader =
|
|
106
|
+
hasMascot &&
|
|
107
|
+
renderWidth >= fullHeaderWidth + MASCOT_GAP + heightLimitedMascotWidth &&
|
|
108
|
+
terminalRows >= variant.header.length;
|
|
109
|
+
const canShowCompactHeader =
|
|
110
|
+
!canUseFullHeader &&
|
|
111
|
+
terminalColumns >= COMPACT_HEADER_HIDDEN_COLUMNS &&
|
|
112
|
+
renderWidth >= COMPACT_HEADER_HIDDEN_COLUMNS;
|
|
113
|
+
const compactHeaderRows = stackedHeaderRowBudget(terminalRows);
|
|
114
|
+
const compactHeader =
|
|
115
|
+
canShowCompactHeader && compactHeaderRows >= STACKED_HEADER_MIN_ROWS
|
|
116
|
+
? scaleHeader(variantName, renderWidth, compactHeaderRows)
|
|
117
|
+
: undefined;
|
|
118
|
+
const contentRowBudget = Math.min(terminalRows, STACKED_CONTENT_MAX_ROWS);
|
|
119
|
+
return {
|
|
120
|
+
canUseFullHeader,
|
|
121
|
+
hasMascot,
|
|
122
|
+
compactHeader,
|
|
123
|
+
fullHeaderWidth,
|
|
124
|
+
mascotRowBudget: canUseFullHeader
|
|
125
|
+
? terminalRows
|
|
126
|
+
: Math.max(1, contentRowBudget - (compactHeader?.header.length ?? 0)),
|
|
127
|
+
mascotWidthBudget: canUseFullHeader
|
|
128
|
+
? Math.max(1, renderWidth - fullHeaderWidth - MASCOT_GAP)
|
|
129
|
+
: compactMascotWidthBudget(renderWidth),
|
|
130
|
+
renderWidth,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function renderMascotLines(
|
|
135
|
+
variant: HeaderConfig,
|
|
136
|
+
mascotArt: ReturnType<typeof scaleMascot>,
|
|
137
|
+
frame: number,
|
|
138
|
+
theme: SimpleTheme,
|
|
139
|
+
animationStyle: "animated" | "static",
|
|
140
|
+
): string[] {
|
|
141
|
+
return mascotArt.mascot.map((line, index) => {
|
|
142
|
+
const toneLine = mascotArt.toneMap[index];
|
|
143
|
+
if (toneLine && variant.mascotTonePalette) {
|
|
144
|
+
return animateAsciiLineWithToneMap(
|
|
145
|
+
line,
|
|
146
|
+
toneLine,
|
|
147
|
+
index,
|
|
148
|
+
frame,
|
|
149
|
+
theme,
|
|
150
|
+
variant.mascotTonePalette,
|
|
151
|
+
animationStyle,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return animateAsciiLine(
|
|
155
|
+
line,
|
|
156
|
+
index,
|
|
157
|
+
frame,
|
|
158
|
+
theme,
|
|
159
|
+
variant.mascotPalette.baseColor,
|
|
160
|
+
variant.mascotPalette.highlightColor,
|
|
161
|
+
variant.mascotPalette.trailColor,
|
|
162
|
+
animationStyle,
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function renderHeaderLines(
|
|
168
|
+
variant: HeaderConfig,
|
|
169
|
+
header: readonly string[],
|
|
170
|
+
toneMap: readonly string[] | undefined,
|
|
171
|
+
sourceIndexes: readonly number[],
|
|
172
|
+
frame: number,
|
|
173
|
+
theme: SimpleTheme,
|
|
174
|
+
animationStyle: "animated" | "static",
|
|
175
|
+
): string[] {
|
|
176
|
+
return header.map((line, index) => {
|
|
177
|
+
const sourceIndex = sourceIndexes[index] ?? index;
|
|
178
|
+
const toneLine = toneMap?.[index];
|
|
179
|
+
if (toneLine && variant.headerTonePalette) {
|
|
180
|
+
return animateAsciiLineWithToneMap(
|
|
181
|
+
line,
|
|
182
|
+
toneLine,
|
|
183
|
+
index,
|
|
184
|
+
frame,
|
|
185
|
+
theme,
|
|
186
|
+
variant.headerTonePalette,
|
|
187
|
+
animationStyle,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
const palette = variant.linePalette(sourceIndex);
|
|
191
|
+
return animateAsciiLine(
|
|
192
|
+
line,
|
|
193
|
+
index,
|
|
194
|
+
frame,
|
|
195
|
+
theme,
|
|
196
|
+
palette.baseColor,
|
|
197
|
+
palette.highlightColor,
|
|
198
|
+
palette.trailColor,
|
|
199
|
+
animationStyle,
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function renderResponsiveHeader(
|
|
205
|
+
state: OsdyState,
|
|
206
|
+
width: number,
|
|
207
|
+
terminalColumns: number,
|
|
208
|
+
terminalRows: number,
|
|
209
|
+
frame: number,
|
|
210
|
+
theme: SimpleTheme,
|
|
211
|
+
animationStyle: "animated" | "static",
|
|
212
|
+
): string[] {
|
|
213
|
+
const variant = HEADER_VARIANTS[state.headerVariant];
|
|
214
|
+
const layout = calculateHeaderLayout(
|
|
215
|
+
state.headerVariant,
|
|
216
|
+
variant,
|
|
217
|
+
width,
|
|
218
|
+
terminalColumns,
|
|
219
|
+
terminalRows,
|
|
220
|
+
);
|
|
221
|
+
const mascotArt = layout.hasMascot
|
|
222
|
+
? scaleMascot(
|
|
223
|
+
{ mascot: variant.mascot ?? [], toneMap: variant.mascotMap ?? [] },
|
|
224
|
+
layout.mascotWidthBudget,
|
|
225
|
+
layout.mascotRowBudget,
|
|
226
|
+
)
|
|
227
|
+
: { mascot: [], toneMap: [] };
|
|
228
|
+
const mascotLines = renderMascotLines(
|
|
229
|
+
variant,
|
|
230
|
+
mascotArt,
|
|
231
|
+
frame,
|
|
232
|
+
theme,
|
|
233
|
+
animationStyle,
|
|
234
|
+
);
|
|
235
|
+
if (!layout.canUseFullHeader && !layout.compactHeader) {
|
|
236
|
+
return mascotLines.map((line) => fitCenterVisible(line, layout.renderWidth));
|
|
237
|
+
}
|
|
238
|
+
const headerArt = layout.compactHeader;
|
|
239
|
+
const logoLines = layout.canUseFullHeader
|
|
240
|
+
? renderHeaderLines(
|
|
241
|
+
variant,
|
|
242
|
+
variant.header,
|
|
243
|
+
variant.headerMap,
|
|
244
|
+
variant.header.map((_line, index) => index),
|
|
245
|
+
frame,
|
|
246
|
+
theme,
|
|
247
|
+
animationStyle,
|
|
248
|
+
)
|
|
249
|
+
: renderHeaderLines(
|
|
250
|
+
variant,
|
|
251
|
+
headerArt?.header ?? [],
|
|
252
|
+
headerArt?.toneMap,
|
|
253
|
+
headerArt?.sourceIndexes ?? [],
|
|
254
|
+
frame,
|
|
255
|
+
theme,
|
|
256
|
+
animationStyle,
|
|
257
|
+
);
|
|
258
|
+
if (!layout.canUseFullHeader) {
|
|
259
|
+
return [...mascotLines, ...logoLines].map((line) =>
|
|
260
|
+
fitCenterVisible(line, layout.renderWidth),
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
const scaledMascotWidth = mascotArt.mascot.reduce(
|
|
264
|
+
(maximum, line) => Math.max(maximum, visibleWidth(line)),
|
|
265
|
+
0,
|
|
266
|
+
);
|
|
267
|
+
return composeSideBySide(
|
|
268
|
+
mascotLines,
|
|
269
|
+
scaledMascotWidth,
|
|
270
|
+
logoLines,
|
|
271
|
+
layout.renderWidth,
|
|
272
|
+
layout.fullHeaderWidth,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
47
276
|
class OsdyFooter implements Component {
|
|
277
|
+
private readonly pi: ExtensionAPI;
|
|
48
278
|
private readonly ctx: ExtensionContext;
|
|
279
|
+
private readonly state: OsdyState;
|
|
49
280
|
private readonly footerData: ReadonlyFooterDataProvider;
|
|
50
281
|
private readonly theme: SimpleTheme;
|
|
51
282
|
|
|
52
283
|
constructor(
|
|
284
|
+
pi: ExtensionAPI,
|
|
53
285
|
ctx: ExtensionContext,
|
|
286
|
+
state: OsdyState,
|
|
54
287
|
footerData: ReadonlyFooterDataProvider,
|
|
55
288
|
theme: SimpleTheme,
|
|
56
289
|
) {
|
|
290
|
+
this.pi = pi;
|
|
57
291
|
this.ctx = ctx;
|
|
292
|
+
this.state = state;
|
|
58
293
|
this.footerData = footerData;
|
|
59
294
|
this.theme = theme;
|
|
60
295
|
}
|
|
61
296
|
|
|
62
297
|
render(width: number): string[] {
|
|
298
|
+
const ellipsis = this.theme.fg("dim", "...");
|
|
63
299
|
let location = formatPath(this.ctx.cwd);
|
|
64
300
|
const branch = this.footerData.getGitBranch?.();
|
|
65
301
|
if (branch) location = `${location} (${branch})`;
|
|
66
302
|
const locationLine = truncateToWidth(
|
|
67
303
|
this.theme.fg("dim", location),
|
|
68
304
|
width,
|
|
69
|
-
|
|
305
|
+
ellipsis,
|
|
70
306
|
);
|
|
71
307
|
const statuses = this.footerData.getExtensionStatuses?.() ?? new Map();
|
|
72
|
-
const statusEntries = Array.from(statuses.entries())
|
|
308
|
+
const statusEntries = Array.from(statuses.entries()).filter(
|
|
309
|
+
([key]) => !this.state.smallMode || key !== PI_LENS_STATUS_KEY,
|
|
310
|
+
);
|
|
73
311
|
const statusLine = statusEntries
|
|
74
312
|
.sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey))
|
|
75
313
|
.map(([, text]) => sanitizeStatusText(text))
|
|
76
314
|
.join(" ");
|
|
315
|
+
if (!shouldShowFooterMetadata(this.state.editorEffective)) {
|
|
316
|
+
return statusLine
|
|
317
|
+
? [locationLine, truncateToWidth(statusLine, width, ellipsis)]
|
|
318
|
+
: [locationLine];
|
|
319
|
+
}
|
|
320
|
+
const thinkingLevel = this.pi.getThinkingLevel();
|
|
321
|
+
const thinkingLine = truncateToWidth(
|
|
322
|
+
formatModelMetadata(
|
|
323
|
+
modelLabel(this.ctx),
|
|
324
|
+
this.theme.fg(
|
|
325
|
+
THINKING_THEME_TOKENS[thinkingLevel],
|
|
326
|
+
`\u001B[1m${thinkingLevel}\u001B[22m`,
|
|
327
|
+
),
|
|
328
|
+
resolveActiveProfileLabel(),
|
|
329
|
+
),
|
|
330
|
+
width,
|
|
331
|
+
ellipsis,
|
|
332
|
+
);
|
|
333
|
+
const usageLine = truncateToWidth(
|
|
334
|
+
usageLabel(this.ctx).trim(),
|
|
335
|
+
width,
|
|
336
|
+
ellipsis,
|
|
337
|
+
);
|
|
77
338
|
return statusLine
|
|
78
339
|
? [
|
|
340
|
+
thinkingLine,
|
|
341
|
+
usageLine,
|
|
79
342
|
locationLine,
|
|
80
|
-
truncateToWidth(statusLine, width,
|
|
343
|
+
truncateToWidth(statusLine, width, ellipsis),
|
|
81
344
|
]
|
|
82
|
-
: [locationLine];
|
|
345
|
+
: [thinkingLine, usageLine, locationLine];
|
|
83
346
|
}
|
|
84
347
|
|
|
85
348
|
invalidate(): void {}
|
|
@@ -97,9 +360,7 @@ class OsdyWorkingWidget implements Component {
|
|
|
97
360
|
render(width: number): string[] {
|
|
98
361
|
if (!this.state.active) return [];
|
|
99
362
|
const frame =
|
|
100
|
-
WORKING_SPINNER_FRAMES[
|
|
101
|
-
this.state.frame % WORKING_SPINNER_FRAMES.length
|
|
102
|
-
] ??
|
|
363
|
+
WORKING_SPINNER_FRAMES[this.state.frame % WORKING_SPINNER_FRAMES.length] ??
|
|
103
364
|
WORKING_SPINNER_FRAMES[0] ??
|
|
104
365
|
"⠋";
|
|
105
366
|
const line = `${this.theme.fg("accent", frame)} ${this.theme.fg("muted", this.state.label)}`;
|
|
@@ -110,11 +371,12 @@ class OsdyWorkingWidget implements Component {
|
|
|
110
371
|
}
|
|
111
372
|
|
|
112
373
|
export function createHeaderComponent(
|
|
113
|
-
|
|
114
|
-
|
|
374
|
+
_pi: ExtensionAPI,
|
|
375
|
+
_ctx: ExtensionContext,
|
|
115
376
|
state: OsdyState,
|
|
116
377
|
) {
|
|
117
378
|
return (_tui: TUI, theme: SimpleTheme) => {
|
|
379
|
+
state.tui = _tui;
|
|
118
380
|
let frame = 0;
|
|
119
381
|
let animationComplete = false;
|
|
120
382
|
const animationMode = asciiAnimationMode();
|
|
@@ -133,93 +395,15 @@ export function createHeaderComponent(
|
|
|
133
395
|
render(width: number): string[] {
|
|
134
396
|
const animationStyle =
|
|
135
397
|
animateAscii && !animationComplete ? "animated" : "static";
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const showMascot =
|
|
146
|
-
canUseFullHeader &&
|
|
147
|
-
mascotLinesSource.length > 0 &&
|
|
148
|
-
_tui.terminal.rows >= MASCOT_MIN_ROWS &&
|
|
149
|
-
width >= fullMascotWidth + MASCOT_GAP + fullHeaderWidth;
|
|
150
|
-
const headerLines = canUseFullHeader
|
|
151
|
-
? [...variant.header]
|
|
152
|
-
: [...(variant.fallbackHeader ?? HEADER_FALLBACK)];
|
|
153
|
-
const headerToneMap = canUseFullHeader ? (variant.headerMap ?? []) : [];
|
|
154
|
-
const metadataRows = renderHeaderMetadata(pi, ctx, state, width);
|
|
155
|
-
const logoLines = headerLines.map((line, index) => {
|
|
156
|
-
const toneLine = headerToneMap[index];
|
|
157
|
-
if (toneLine && variant.headerTonePalette) {
|
|
158
|
-
return animateAsciiLineWithToneMap(
|
|
159
|
-
line,
|
|
160
|
-
toneLine,
|
|
161
|
-
index,
|
|
162
|
-
frame,
|
|
163
|
-
theme,
|
|
164
|
-
variant.headerTonePalette,
|
|
165
|
-
animationStyle,
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
const palette = canUseFullHeader
|
|
169
|
-
? variant.linePalette(index)
|
|
170
|
-
: (variant.fallbackLinePalette?.(index) ??
|
|
171
|
-
variant.linePalette(index));
|
|
172
|
-
return animateAsciiLine(
|
|
173
|
-
line,
|
|
174
|
-
index,
|
|
175
|
-
frame,
|
|
176
|
-
theme,
|
|
177
|
-
palette.baseColor,
|
|
178
|
-
palette.highlightColor,
|
|
179
|
-
palette.trailColor,
|
|
180
|
-
animationStyle,
|
|
181
|
-
);
|
|
182
|
-
});
|
|
183
|
-
const mascotLines = mascotLinesSource.map((line, index) => {
|
|
184
|
-
const toneLine = mascotToneMap[index];
|
|
185
|
-
if (toneLine && variant.mascotTonePalette) {
|
|
186
|
-
return animateAsciiLineWithToneMap(
|
|
187
|
-
line,
|
|
188
|
-
toneLine,
|
|
189
|
-
index,
|
|
190
|
-
frame,
|
|
191
|
-
theme,
|
|
192
|
-
variant.mascotTonePalette,
|
|
193
|
-
animationStyle,
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
return animateAsciiLine(
|
|
197
|
-
line,
|
|
198
|
-
index,
|
|
199
|
-
frame,
|
|
200
|
-
theme,
|
|
201
|
-
variant.mascotPalette.baseColor,
|
|
202
|
-
variant.mascotPalette.highlightColor,
|
|
203
|
-
variant.mascotPalette.trailColor,
|
|
204
|
-
animationStyle,
|
|
205
|
-
);
|
|
206
|
-
});
|
|
207
|
-
const headerBlock = showMascot
|
|
208
|
-
? composeSideBySide(
|
|
209
|
-
mascotLines,
|
|
210
|
-
fullMascotWidth,
|
|
211
|
-
logoLines,
|
|
212
|
-
width,
|
|
213
|
-
fullHeaderWidth,
|
|
214
|
-
)
|
|
215
|
-
: logoLines.map((line) => fitCenterVisible(line, width));
|
|
216
|
-
return [
|
|
217
|
-
"",
|
|
218
|
-
...headerBlock,
|
|
219
|
-
"",
|
|
220
|
-
...renderMetaRows(metadataRows, width, theme),
|
|
221
|
-
"",
|
|
222
|
-
];
|
|
398
|
+
return renderResponsiveHeader(
|
|
399
|
+
state,
|
|
400
|
+
width,
|
|
401
|
+
Math.max(1, _tui.terminal.columns),
|
|
402
|
+
Math.max(1, _tui.terminal.rows),
|
|
403
|
+
frame,
|
|
404
|
+
theme,
|
|
405
|
+
animationStyle,
|
|
406
|
+
);
|
|
223
407
|
},
|
|
224
408
|
invalidate() {},
|
|
225
409
|
dispose() {
|
|
@@ -230,11 +414,13 @@ export function createHeaderComponent(
|
|
|
230
414
|
}
|
|
231
415
|
|
|
232
416
|
export function createFooterComponent(
|
|
417
|
+
pi: ExtensionAPI,
|
|
233
418
|
ctx: ExtensionContext,
|
|
419
|
+
state: OsdyState,
|
|
234
420
|
footerData: ReadonlyFooterDataProvider,
|
|
235
421
|
theme: SimpleTheme,
|
|
236
422
|
): Component {
|
|
237
|
-
return new OsdyFooter(ctx, footerData, theme);
|
|
423
|
+
return new OsdyFooter(pi, ctx, state, footerData, theme);
|
|
238
424
|
}
|
|
239
425
|
|
|
240
426
|
export function createWorkingWidgetFactory(workingState: WorkingWidgetState) {
|
|
@@ -275,10 +461,7 @@ export function createEditorComponent(
|
|
|
275
461
|
const internalLines = bottomIndex - 1;
|
|
276
462
|
if (internalLines < target) {
|
|
277
463
|
const blank = " ".repeat(innerWidth);
|
|
278
|
-
const added = Array.from(
|
|
279
|
-
{ length: target - internalLines },
|
|
280
|
-
() => blank,
|
|
281
|
-
);
|
|
464
|
+
const added = Array.from({ length: target - internalLines }, () => blank);
|
|
282
465
|
lines.splice(bottomIndex, 0, ...added);
|
|
283
466
|
bottomIndex += added.length;
|
|
284
467
|
}
|
|
@@ -286,12 +469,13 @@ export function createEditorComponent(
|
|
|
286
469
|
const side = borderColor("│");
|
|
287
470
|
for (let index = 1; index < bottomIndex; index += 1) {
|
|
288
471
|
const content = truncateToWidth(lines[index] ?? "", innerWidth, "");
|
|
289
|
-
const padding = " ".repeat(
|
|
290
|
-
Math.max(0, innerWidth - visibleWidth(content)),
|
|
291
|
-
);
|
|
472
|
+
const padding = " ".repeat(Math.max(0, innerWidth - visibleWidth(content)));
|
|
292
473
|
lines[index] = `${side}${content}${padding}${side}`;
|
|
293
474
|
}
|
|
294
|
-
const topLeft = ctx.ui.theme.fg(
|
|
475
|
+
const topLeft = ctx.ui.theme.fg(
|
|
476
|
+
"mdLink",
|
|
477
|
+
` ${resolveEditorTitleLabel(resolveActiveProfileLabel())} `,
|
|
478
|
+
);
|
|
295
479
|
const topRight = ctx.ui.theme.fg(
|
|
296
480
|
"muted",
|
|
297
481
|
` ${modelLabel(ctx)} · think ${pi.getThinkingLevel()} `,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TUI } from "@earendil-works/pi-tui";
|
|
2
2
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
-
import { MASCOT_GAP } from "./constants.js";
|
|
3
|
+
import { headerWidth, HEADER_VARIANTS, MASCOT_GAP } from "./constants.js";
|
|
4
|
+
import type { HeaderVariant } from "./types.js";
|
|
4
5
|
|
|
5
6
|
export function sanitizeStatusText(text: string): string {
|
|
6
7
|
return text
|
|
@@ -49,6 +50,47 @@ export function composeSideBySide(
|
|
|
49
50
|
return composedRows;
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
export function isSmallResponsiveMode(
|
|
54
|
+
variant: HeaderVariant,
|
|
55
|
+
columns: number,
|
|
56
|
+
rows: number,
|
|
57
|
+
): boolean {
|
|
58
|
+
const header = HEADER_VARIANTS[variant];
|
|
59
|
+
const hasMascot =
|
|
60
|
+
(header.mascot?.length ?? 0) > 0 && (header.mascotMap?.length ?? 0) > 0;
|
|
61
|
+
return (
|
|
62
|
+
!hasMascot ||
|
|
63
|
+
columns < headerWidth(variant) + MASCOT_GAP + 1 ||
|
|
64
|
+
rows < header.header.length
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// In mascot-only mode, phone-sized terminals use roughly four-fifths of the
|
|
69
|
+
// available width while wider compact terminals gradually recover more detail.
|
|
70
|
+
const COMPACT_MASCOT_BASE_RATIO = 0.7938;
|
|
71
|
+
const COMPACT_MASCOT_MAX_RATIO = 0.87318;
|
|
72
|
+
const COMPACT_MASCOT_RATIO_RAMP_START_COLUMNS = 60;
|
|
73
|
+
const COMPACT_MASCOT_RATIO_RAMP_COLUMNS = 40;
|
|
74
|
+
|
|
75
|
+
export function compactMascotWidthBudget(availableWidth: number): number {
|
|
76
|
+
const normalizedWidth = Math.max(1, Math.floor(availableWidth));
|
|
77
|
+
const ratioProgress = Math.max(
|
|
78
|
+
0,
|
|
79
|
+
Math.min(
|
|
80
|
+
1,
|
|
81
|
+
(normalizedWidth - COMPACT_MASCOT_RATIO_RAMP_START_COLUMNS) /
|
|
82
|
+
COMPACT_MASCOT_RATIO_RAMP_COLUMNS,
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
const widthRatio =
|
|
86
|
+
COMPACT_MASCOT_BASE_RATIO +
|
|
87
|
+
(COMPACT_MASCOT_MAX_RATIO - COMPACT_MASCOT_BASE_RATIO) * ratioProgress;
|
|
88
|
+
return Math.min(
|
|
89
|
+
normalizedWidth,
|
|
90
|
+
Math.max(1, Math.round(normalizedWidth * widthRatio)),
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
52
94
|
export function internalLineTarget(tui: TUI): number {
|
|
53
95
|
return tui.terminal.rows < 18 ? 3 : 4;
|
|
54
96
|
}
|
|
@@ -11,6 +11,8 @@ import type {
|
|
|
11
11
|
WorkingTreeFileSummary,
|
|
12
12
|
WorkingTreeSnapshot,
|
|
13
13
|
WorkingTreeState,
|
|
14
|
+
WorkingWidgetState,
|
|
15
|
+
WorkingTreePlacement,
|
|
14
16
|
} from "./types.js";
|
|
15
17
|
import { fitCenterVisible } from "./utils.js";
|
|
16
18
|
|
|
@@ -128,13 +130,16 @@ async function hydrateUntrackedStats(
|
|
|
128
130
|
): Promise<void> {
|
|
129
131
|
await Promise.all(
|
|
130
132
|
Array.from(fileMap.values()).map(async (file) => {
|
|
131
|
-
if (!file.untracked || file.additions > 0 || file.removals > 0)
|
|
133
|
+
if (!file.untracked || file.additions > 0 || file.removals > 0) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
132
136
|
try {
|
|
133
137
|
const content = await readFile(resolve(ctx.cwd, file.path), "utf8");
|
|
134
138
|
file.additions = content.length === 0 ? 0 : content.split("\n").length;
|
|
135
139
|
} catch {
|
|
136
140
|
file.additions = 1;
|
|
137
141
|
}
|
|
142
|
+
return undefined;
|
|
138
143
|
}),
|
|
139
144
|
);
|
|
140
145
|
}
|
|
@@ -231,8 +236,8 @@ function renderSummaryLine(
|
|
|
231
236
|
}
|
|
232
237
|
const parts = [
|
|
233
238
|
theme.fg("mdLink", `Git changes · ${snapshot.totalFiles} files`),
|
|
234
|
-
theme.fg("
|
|
235
|
-
theme.fg("
|
|
239
|
+
theme.fg("toolDiffAdded", `+${snapshot.additions}`),
|
|
240
|
+
theme.fg("toolDiffRemoved", `-${snapshot.removals}`),
|
|
236
241
|
theme.fg("mdLink", `staged ${snapshot.stagedFiles}`),
|
|
237
242
|
theme.fg("mdLink", `unstaged ${snapshot.unstagedFiles}`),
|
|
238
243
|
];
|
|
@@ -249,7 +254,7 @@ class WorkingTreeWidget implements Component {
|
|
|
249
254
|
) {}
|
|
250
255
|
|
|
251
256
|
render(width: number): string[] {
|
|
252
|
-
if (!this.state.enabled) return [];
|
|
257
|
+
if (!this.state.enabled || !this.state.visible) return [];
|
|
253
258
|
if (this.state.loading) {
|
|
254
259
|
return [
|
|
255
260
|
fitCenterVisible(
|
|
@@ -282,9 +287,23 @@ class WorkingTreeWidget implements Component {
|
|
|
282
287
|
invalidate(): void {}
|
|
283
288
|
}
|
|
284
289
|
|
|
285
|
-
export function createWorkingTreeWidgetFactory(
|
|
290
|
+
export function createWorkingTreeWidgetFactory(
|
|
291
|
+
state: WorkingTreeState,
|
|
292
|
+
workingState: WorkingWidgetState,
|
|
293
|
+
placement: WorkingTreePlacement,
|
|
294
|
+
) {
|
|
286
295
|
return (tui: TUI, theme: SimpleTheme): Component => {
|
|
287
296
|
state.tui = tui;
|
|
288
|
-
|
|
297
|
+
const summary = new WorkingTreeWidget(state, theme);
|
|
298
|
+
return {
|
|
299
|
+
render(width: number): string[] {
|
|
300
|
+
const lines = summary.render(width);
|
|
301
|
+
if (lines.length === 0) return [];
|
|
302
|
+
const needsLeadingBlank =
|
|
303
|
+
placement === "belowEditor" || workingState.active;
|
|
304
|
+
return [...(needsLeadingBlank ? [""] : []), ...lines, ""];
|
|
305
|
+
},
|
|
306
|
+
invalidate(): void {},
|
|
307
|
+
};
|
|
289
308
|
};
|
|
290
309
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "osdy-pi",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Osdy Pi package: themes, header, and custom editor for Pi.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -10,23 +10,40 @@
|
|
|
10
10
|
"theme",
|
|
11
11
|
"extension"
|
|
12
12
|
],
|
|
13
|
+
"bin": {
|
|
14
|
+
"osdy-pi": "bin/osdy-pi.mjs"
|
|
15
|
+
},
|
|
13
16
|
"scripts": {
|
|
14
17
|
"typecheck": "tsc --noEmit",
|
|
15
18
|
"lint": "eslint .",
|
|
19
|
+
"test": "node --test --experimental-strip-types extensions/osdy-pi/*.test.ts && node --test scripts/*.test.mjs",
|
|
16
20
|
"pi:dev": "node ./scripts/pi-dev.mjs"
|
|
17
21
|
},
|
|
18
22
|
"files": [
|
|
19
23
|
"README.md",
|
|
20
24
|
"LICENSE",
|
|
25
|
+
"bin",
|
|
26
|
+
"scripts/osdy-pi-account-profiles.mjs",
|
|
21
27
|
"themes",
|
|
22
28
|
"extensions",
|
|
23
29
|
"mapche1.png"
|
|
24
30
|
],
|
|
25
31
|
"pi": {
|
|
26
32
|
"themes": [
|
|
33
|
+
"./themes/osdy-pi-new.json",
|
|
27
34
|
"./themes/osdy-pi-dark.json",
|
|
28
|
-
"./themes/osdy-pi-
|
|
29
|
-
"./themes/osdy-pi-
|
|
35
|
+
"./themes/osdy-pi-sexy.json",
|
|
36
|
+
"./themes/osdy-pi-tokyo-night.json",
|
|
37
|
+
"./themes/osdy-pi-kanagawa-wave.json",
|
|
38
|
+
"./themes/osdy-pi-kanagawa-dragon.json",
|
|
39
|
+
"./themes/osdy-pi-kanagawa-lotus.json",
|
|
40
|
+
"./themes/osdy-pi-dracula.json",
|
|
41
|
+
"./themes/osdy-pi-catppuccin-latte.json",
|
|
42
|
+
"./themes/osdy-pi-catppuccin-frappe.json",
|
|
43
|
+
"./themes/osdy-pi-catppuccin-macchiato.json",
|
|
44
|
+
"./themes/osdy-pi-catppuccin-mocha.json",
|
|
45
|
+
"./themes/osdy-pi-matrix.json",
|
|
46
|
+
"./themes/osdy-pi-lucent-orange.json"
|
|
30
47
|
],
|
|
31
48
|
"extensions": [
|
|
32
49
|
"./extensions/osdy-pi.ts"
|
|
@@ -38,8 +55,8 @@
|
|
|
38
55
|
"@earendil-works/pi-tui": "*"
|
|
39
56
|
},
|
|
40
57
|
"devDependencies": {
|
|
41
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
42
|
-
"@earendil-works/pi-tui": "^0.
|
|
58
|
+
"@earendil-works/pi-coding-agent": "^0.84.4",
|
|
59
|
+
"@earendil-works/pi-tui": "^0.84.4",
|
|
43
60
|
"@eslint/js": "^9.39.1",
|
|
44
61
|
"@types/node": "^25.8.0",
|
|
45
62
|
"eslint": "^9.39.1",
|