@narumitw/pi-statusline 0.49.5 → 0.49.7
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 +6 -6
- package/package.json +8 -5
- package/src/commands.ts +38 -58
- package/src/directory.ts +1 -1
- package/src/render.ts +1 -1
- package/src/terminal.ts +0 -60
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ A representative uncolored layout:
|
|
|
22
22
|
available under **Advanced**.
|
|
23
23
|
|
|
24
24
|
> **Need more customization?** See
|
|
25
|
-
> [`pi-starship`](https://github.com/narumiruna/pi-extensions/tree/main/
|
|
25
|
+
> [`pi-starship`](https://github.com/narumiruna/pi-extensions/tree/main/packages/pi-starship)
|
|
26
26
|
> ([npm](https://www.npmjs.com/package/@narumitw/pi-starship)). It uses a
|
|
27
27
|
> [Starship-inspired](https://starship.rs/) TOML format and style syntax for deeper control over
|
|
28
28
|
> layout, modules, and colors. Choose `pi-statusline` for practical defaults and quick setup.
|
|
@@ -49,7 +49,7 @@ For the best result, use a terminal font that includes Powerline glyphs and emoj
|
|
|
49
49
|
1. Install the extension and start Pi.
|
|
50
50
|
2. Run `/statusline`.
|
|
51
51
|
3. Use the standard Main/Advanced navigation, then choose an appearance or information level;
|
|
52
|
-
|
|
52
|
+
appearance previews update the extension-owned footer before you apply a choice.
|
|
53
53
|
|
|
54
54
|
```text
|
|
55
55
|
Appearance (tokyo-night)
|
|
@@ -93,9 +93,9 @@ reads or writes.
|
|
|
93
93
|
|
|
94
94
|
The direct `settings`, `status`, and `help` routes remain for compatibility. The root standard menu
|
|
95
95
|
is TUI-only; Escape returns from Advanced or closes Main. RPC receives observable notifications
|
|
96
|
-
instead of TUI-only controls. Unknown subcommands and trailing arguments are rejected.
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
instead of TUI-only controls. Unknown subcommands and trailing arguments are rejected. The standard
|
|
97
|
+
palette picker owns navigation and cleanup while pi-statusline still owns footer previews, settings,
|
|
98
|
+
and rollback. The width-aware layout editor and JSON editor remain specialized UI.
|
|
99
99
|
|
|
100
100
|
## 📐 Runtime behavior
|
|
101
101
|
|
|
@@ -334,7 +334,7 @@ Put transient activity in the value and always clear the same complete key.
|
|
|
334
334
|
## 🗂️ Package layout
|
|
335
335
|
|
|
336
336
|
```text
|
|
337
|
-
|
|
337
|
+
packages/pi-statusline/
|
|
338
338
|
├── src/
|
|
339
339
|
│ ├── index.ts # thin entrypoint forwarding to the source runtime
|
|
340
340
|
│ ├── statusline.ts # authoritative lifecycle implementation
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@narumitw/pi-statusline",
|
|
3
|
-
"version": "0.49.
|
|
3
|
+
"version": "0.49.7",
|
|
4
4
|
"description": "Pi extension that replaces the footer with an information-rich statusline.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
"./src/index.ts"
|
|
23
23
|
]
|
|
24
24
|
},
|
|
25
|
+
"piExtension": {
|
|
26
|
+
"lifecycle": "stable"
|
|
27
|
+
},
|
|
25
28
|
"scripts": {
|
|
26
29
|
"check": "biome check --vcs-use-ignore-file=false src test package.json tsconfig.json README.md && npm run typecheck",
|
|
27
30
|
"format": "biome check --write --vcs-use-ignore-file=false src test package.json tsconfig.json README.md",
|
|
@@ -33,17 +36,17 @@
|
|
|
33
36
|
},
|
|
34
37
|
"devDependencies": {
|
|
35
38
|
"@biomejs/biome": "2.5.7",
|
|
36
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
37
|
-
"@earendil-works/pi-tui": "0.
|
|
39
|
+
"@earendil-works/pi-coding-agent": "0.84.1",
|
|
40
|
+
"@earendil-works/pi-tui": "0.84.1",
|
|
38
41
|
"@types/node": "26.1.2",
|
|
39
42
|
"typescript": "7.0.2"
|
|
40
43
|
},
|
|
41
44
|
"repository": {
|
|
42
45
|
"type": "git",
|
|
43
46
|
"url": "https://github.com/narumiruna/pi-extensions",
|
|
44
|
-
"directory": "
|
|
47
|
+
"directory": "packages/pi-statusline"
|
|
45
48
|
},
|
|
46
49
|
"dependencies": {
|
|
47
|
-
"@narumitw/pi-tui-kit": "^0.
|
|
50
|
+
"@narumitw/pi-tui-kit": "^0.54.0"
|
|
48
51
|
}
|
|
49
52
|
}
|
package/src/commands.ts
CHANGED
|
@@ -7,8 +7,6 @@ import {
|
|
|
7
7
|
Container,
|
|
8
8
|
Key,
|
|
9
9
|
matchesKey,
|
|
10
|
-
type SelectItem,
|
|
11
|
-
SelectList,
|
|
12
10
|
Text,
|
|
13
11
|
truncateToWidth,
|
|
14
12
|
wrapTextWithAnsi,
|
|
@@ -56,13 +54,18 @@ const SEGMENT_DESCRIPTIONS: Record<SegmentName, string> = {
|
|
|
56
54
|
time: "Current local time",
|
|
57
55
|
turn: "Current session turn count",
|
|
58
56
|
};
|
|
57
|
+
interface StatuslineMenuOwner {
|
|
58
|
+
signal: AbortSignal;
|
|
59
|
+
isCurrent(): boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
59
62
|
export interface StatuslineCommandOptions {
|
|
60
63
|
settingsPath: string;
|
|
61
64
|
getLoaded(): LoadedStatuslineSettings;
|
|
62
65
|
apply(loaded: LoadedStatuslineSettings, ctx: ExtensionCommandContext): void;
|
|
63
66
|
preview?(palettePreset: PalettePreset | undefined, ctx: ExtensionCommandContext): void;
|
|
64
67
|
save?: (settingsPath: string, rawDocument: string) => LoadedStatuslineSettings;
|
|
65
|
-
getMenuOwner?():
|
|
68
|
+
getMenuOwner?(): StatuslineMenuOwner;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
export function registerStatuslineCommand(pi: ExtensionAPI, options: StatuslineCommandOptions) {
|
|
@@ -191,7 +194,7 @@ async function showMainMenu(ctx: ExtensionCommandContext, options: StatuslineCom
|
|
|
191
194
|
},
|
|
192
195
|
actions: {
|
|
193
196
|
appearance: async () => {
|
|
194
|
-
await choosePalettePreset(ctx, options);
|
|
197
|
+
await choosePalettePreset(ctx, options, owner);
|
|
195
198
|
return { kind: "close" };
|
|
196
199
|
},
|
|
197
200
|
setInformation: async ({ itemId }) => {
|
|
@@ -233,6 +236,7 @@ async function showMainMenu(ctx: ExtensionCommandContext, options: StatuslineCom
|
|
|
233
236
|
async function choosePalettePreset(
|
|
234
237
|
ctx: ExtensionCommandContext,
|
|
235
238
|
options: StatuslineCommandOptions,
|
|
239
|
+
owner: StatuslineMenuOwner,
|
|
236
240
|
) {
|
|
237
241
|
if (ctx.mode !== "tui") {
|
|
238
242
|
if (ctx.hasUI) ctx.ui.notify(`Edit palettePreset manually: ${options.settingsPath}`, "info");
|
|
@@ -241,10 +245,19 @@ async function choosePalettePreset(
|
|
|
241
245
|
const current = options.getLoaded();
|
|
242
246
|
let selection: PalettePreset | undefined;
|
|
243
247
|
try {
|
|
244
|
-
selection = await showPalettePresetPicker(ctx, current.config.palettePreset, options);
|
|
248
|
+
selection = await showPalettePresetPicker(ctx, current.config.palettePreset, options, owner);
|
|
245
249
|
} finally {
|
|
246
|
-
|
|
250
|
+
if (!owner.signal.aborted && owner.isCurrent()) {
|
|
251
|
+
try {
|
|
252
|
+
options.preview?.(undefined, ctx);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
if (canNotify(ctx)) {
|
|
255
|
+
ctx.ui.notify(`Palette preview could not be reset: ${formatError(error)}`, "error");
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
247
259
|
}
|
|
260
|
+
if (owner.signal.aborted || !owner.isCurrent()) return;
|
|
248
261
|
if (selection === undefined) return;
|
|
249
262
|
|
|
250
263
|
let loaded: LoadedStatuslineSettings;
|
|
@@ -278,60 +291,27 @@ async function showPalettePresetPicker(
|
|
|
278
291
|
ctx: ExtensionCommandContext,
|
|
279
292
|
current: PalettePreset,
|
|
280
293
|
options: StatuslineCommandOptions,
|
|
294
|
+
owner: StatuslineMenuOwner,
|
|
281
295
|
): Promise<PalettePreset | undefined> {
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const list = new SelectList(items, Math.min(items.length, 10), {
|
|
300
|
-
selectedPrefix: (text) => theme.fg("accent", text),
|
|
301
|
-
selectedText: (text) => theme.fg("accent", text),
|
|
302
|
-
description: (text) => theme.fg("muted", text),
|
|
303
|
-
scrollInfo: (text) => theme.fg("dim", text),
|
|
304
|
-
noMatch: (text) => theme.fg("warning", text),
|
|
305
|
-
});
|
|
306
|
-
list.setSelectedIndex(selectedIndex);
|
|
307
|
-
list.onSelectionChange = (item) => {
|
|
308
|
-
options.preview?.(item.value as PalettePreset, ctx);
|
|
309
|
-
};
|
|
310
|
-
list.onSelect = (item) => done(item.value as PalettePreset);
|
|
311
|
-
list.onCancel = () => done(null);
|
|
312
|
-
container.addChild(list);
|
|
313
|
-
const hint = new Text("", 1, 0);
|
|
314
|
-
container.addChild(hint);
|
|
315
|
-
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
316
|
-
const updateThemedText = () => {
|
|
317
|
-
title.setText(theme.fg("accent", theme.bold(`Palette preset (current: ${current})`)));
|
|
318
|
-
hint.setText(theme.fg("dim", "↑↓ preview • enter apply • esc cancel"));
|
|
319
|
-
};
|
|
320
|
-
updateThemedText();
|
|
321
|
-
|
|
322
|
-
return {
|
|
323
|
-
render: (width: number) => container.render(width),
|
|
324
|
-
invalidate() {
|
|
325
|
-
container.invalidate();
|
|
326
|
-
updateThemedText();
|
|
327
|
-
},
|
|
328
|
-
handleInput(data: string) {
|
|
329
|
-
list.handleInput(data);
|
|
330
|
-
tui.requestRender();
|
|
331
|
-
},
|
|
332
|
-
};
|
|
296
|
+
const { runLiveChoice } = await import("@narumitw/pi-tui-kit");
|
|
297
|
+
const result = await runLiveChoice(ctx, {
|
|
298
|
+
title: `Palette preset (current: ${current})`,
|
|
299
|
+
items: PALETTE_PRESET_NAMES.map((palettePreset) => ({
|
|
300
|
+
id: palettePreset,
|
|
301
|
+
label: palettePreset,
|
|
302
|
+
description: palettePreset === "custom" ? "per-segment colors from settings JSON" : undefined,
|
|
303
|
+
})),
|
|
304
|
+
currentItemId: current,
|
|
305
|
+
initialItemId: current,
|
|
306
|
+
viewportSize: Math.min(PALETTE_PRESET_NAMES.length, 10),
|
|
307
|
+
hint: "close",
|
|
308
|
+
navigationLabel: "preview",
|
|
309
|
+
confirmLabel: "apply",
|
|
310
|
+
onSelectionChange: ({ item }) => options.preview?.(item.id, ctx),
|
|
311
|
+
signal: owner.signal,
|
|
312
|
+
isCurrent: owner.isCurrent,
|
|
333
313
|
});
|
|
334
|
-
return result
|
|
314
|
+
return result.kind === "selected" ? result.itemId : undefined;
|
|
335
315
|
}
|
|
336
316
|
|
|
337
317
|
function applyInformationProfile(
|
package/src/directory.ts
CHANGED
package/src/render.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
Theme,
|
|
6
6
|
ThemeColor,
|
|
7
7
|
} from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { sanitizeTerminalText } from "@narumitw/pi-tui-kit";
|
|
8
9
|
import { formatDirectoryPath } from "./directory.js";
|
|
9
10
|
import {
|
|
10
11
|
type ExtensionStatusRuntime,
|
|
@@ -13,7 +14,6 @@ import {
|
|
|
13
14
|
} from "./extension-status.js";
|
|
14
15
|
import { formatGitBranchValue, type GitStatusSummary } from "./git-status.js";
|
|
15
16
|
import { renderPowerlineStatusline } from "./powerline.js";
|
|
16
|
-
import { sanitizeTerminalText } from "./terminal.js";
|
|
17
17
|
import {
|
|
18
18
|
LINE_BREAK_SEGMENT_NAME,
|
|
19
19
|
type PowerlineBlockName,
|
package/src/terminal.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
export function sanitizeTerminalText(value: string): string {
|
|
2
|
-
let result = "";
|
|
3
|
-
for (let index = 0; index < value.length; ) {
|
|
4
|
-
const codePoint = value.codePointAt(index) ?? 0;
|
|
5
|
-
const character = String.fromCodePoint(codePoint);
|
|
6
|
-
if (codePoint === 0x1b || codePoint === 0x9b || codePoint === 0x9d) {
|
|
7
|
-
index = skipTerminalEscape(value, index, codePoint);
|
|
8
|
-
continue;
|
|
9
|
-
}
|
|
10
|
-
if (
|
|
11
|
-
codePoint <= 0x1f ||
|
|
12
|
-
(codePoint >= 0x7f && codePoint <= 0x9f) ||
|
|
13
|
-
codePoint === 0x2028 ||
|
|
14
|
-
codePoint === 0x2029
|
|
15
|
-
) {
|
|
16
|
-
if (
|
|
17
|
-
codePoint === 0x09 ||
|
|
18
|
-
codePoint === 0x0a ||
|
|
19
|
-
codePoint === 0x0d ||
|
|
20
|
-
codePoint === 0x85 ||
|
|
21
|
-
codePoint === 0x2028 ||
|
|
22
|
-
codePoint === 0x2029
|
|
23
|
-
) {
|
|
24
|
-
result += " ";
|
|
25
|
-
}
|
|
26
|
-
index += character.length;
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
result += character;
|
|
30
|
-
index += character.length;
|
|
31
|
-
}
|
|
32
|
-
return result;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function skipTerminalEscape(value: string, start: number, codePoint: number): number {
|
|
36
|
-
let index = start + 1;
|
|
37
|
-
const next = value.charCodeAt(index);
|
|
38
|
-
const isOsc = codePoint === 0x9d || (codePoint === 0x1b && next === 0x5d);
|
|
39
|
-
if (isOsc) {
|
|
40
|
-
if (codePoint === 0x1b) index += 1;
|
|
41
|
-
while (index < value.length) {
|
|
42
|
-
const current = value.charCodeAt(index);
|
|
43
|
-
if (current === 0x07 || current === 0x9c) return index + 1;
|
|
44
|
-
if (current === 0x1b && value.charCodeAt(index + 1) === 0x5c) return index + 2;
|
|
45
|
-
index += 1;
|
|
46
|
-
}
|
|
47
|
-
return index;
|
|
48
|
-
}
|
|
49
|
-
const isCsi = codePoint === 0x9b || (codePoint === 0x1b && next === 0x5b);
|
|
50
|
-
if (isCsi) {
|
|
51
|
-
if (codePoint === 0x1b) index += 1;
|
|
52
|
-
while (index < value.length) {
|
|
53
|
-
const current = value.charCodeAt(index);
|
|
54
|
-
index += 1;
|
|
55
|
-
if (current >= 0x40 && current <= 0x7e) break;
|
|
56
|
-
}
|
|
57
|
-
return index;
|
|
58
|
-
}
|
|
59
|
-
return Math.min(value.length, start + (codePoint === 0x1b ? 2 : 1));
|
|
60
|
-
}
|