pi-zentui 0.2.7 → 0.3.1
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 +15 -4
- package/extensions/zentui/config.ts +81 -0
- package/extensions/zentui/footer.ts +24 -16
- package/extensions/zentui/index.ts +15 -6
- package/extensions/zentui/settings-command.ts +102 -11
- package/package.json +19 -9
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ Zentui brings two popular aesthetics to Pi:
|
|
|
22
22
|
- `[!?↑]` — git status indicators (modified, untracked, ahead/behind, stashed, etc.)
|
|
23
23
|
- `via v5.5.0` — runtime detection with version and Starship-style Nerd Font runtime/language modules
|
|
24
24
|
- Right side shows context usage, token counts, and cost
|
|
25
|
+
- Built-in footer segments can be shown or hidden individually from `/zentui`
|
|
25
26
|
- Third-party Pi extension statuses from `ctx.ui.setStatus()` can be shown on the left,
|
|
26
27
|
middle, or right side, or hidden per status key from `/zentui`
|
|
27
28
|
|
|
@@ -124,9 +125,9 @@ pi install git:github.com/lmilojevicc/pi-zentui
|
|
|
124
125
|
|
|
125
126
|
## Config
|
|
126
127
|
|
|
127
|
-
User config lives at `~/.pi/agent/zentui.json`. The file is optional: missing or invalid known values fall back to Zentui defaults, unknown keys are ignored at runtime, and `/zentui` can patch color-source settings, UI feature toggles, and active third-party status placements.
|
|
128
|
+
User config lives at `~/.pi/agent/zentui.json`. The file is optional: missing or invalid known values fall back to Zentui defaults, unknown keys are ignored at runtime, and `/zentui` can patch color-source settings, UI feature toggles, built-in footer segment visibility, and active third-party status placements.
|
|
128
129
|
|
|
129
|
-
The interactive `/zentui` menu is split into
|
|
130
|
+
The interactive `/zentui` menu is split into four sections. Use `Tab` and `Shift+Tab` to switch between `Coloring`, `Features`, `Built-in segments`, and `Extension segments`.
|
|
130
131
|
|
|
131
132
|
Useful slash-command shortcuts:
|
|
132
133
|
|
|
@@ -198,6 +199,15 @@ Default config values — copy this and change any value you want:
|
|
|
198
199
|
"statusLine": true,
|
|
199
200
|
"copyFriendly": false
|
|
200
201
|
},
|
|
202
|
+
"footerSegments": {
|
|
203
|
+
"cwd": true,
|
|
204
|
+
"gitBranch": true,
|
|
205
|
+
"gitStatus": true,
|
|
206
|
+
"runtime": true,
|
|
207
|
+
"context": true,
|
|
208
|
+
"tokens": true,
|
|
209
|
+
"cost": true
|
|
210
|
+
},
|
|
201
211
|
"extensionStatuses": {
|
|
202
212
|
"defaultPlacement": "right",
|
|
203
213
|
"placements": {},
|
|
@@ -211,7 +221,8 @@ Default config values — copy this and change any value you want:
|
|
|
211
221
|
- `icons`: every shown icon key is configurable; omit any key to use the Zentui default. `editorPrompt` controls an optional copy-friendly editor prompt glyph; the default is `""` so copy-friendly mode stays rail-free.
|
|
212
222
|
- `colorSources`: `theme` maps styles through Pi theme tokens; `terminal` emits terminal colors. `/zentui` switches these sources; manual JSON controls specific style values.
|
|
213
223
|
- `features`: `editor` enables Zentui's custom editor, selector borders, and previous-message chrome. `statusLine` enables Zentui's custom footer/status line. `copyFriendly` hides editor and previous-message rail glyphs so native terminal selection copies less chrome. All three can be changed from `/zentui` or direct slash-command arguments.
|
|
214
|
-
- `
|
|
224
|
+
- `footerSegments`: show or hide individual built-in footer segments (`cwd`, `gitBranch`, `gitStatus`, `runtime`, `context`, `tokens`, `cost`). Toggle them from the `Built-in segments` tab in `/zentui`.
|
|
225
|
+
- `extensionStatuses`: controls third-party statuses published by other Pi extensions through `ctx.ui.setStatus()`. `defaultPlacement` and each `placements` value can be `off`, `left`, `middle`, or `right`. The `Extension segments` tab in `/zentui` lists only statuses that are currently active.
|
|
215
226
|
- The shown `editor*` values match the default `theme` source. Omit those keys to keep Zentui's source-aware defaults when switching between `theme` and `terminal`.
|
|
216
227
|
- `editorAccent` styles the active editor rail and previous user-message rail when `features.copyFriendly` is disabled.
|
|
217
228
|
- `editorPrompt` styles the copy-friendly editor prompt glyph. Omit it to use `editorAccent`, then the default accent fallback.
|
|
@@ -222,7 +233,7 @@ Tip: when using copy-friendly mode, setting Pi's `editorPaddingX` to `1` in `~/.
|
|
|
222
233
|
|
|
223
234
|
## Requirements
|
|
224
235
|
|
|
225
|
-
- [Pi](https://pi.dev) coding agent 0.
|
|
236
|
+
- [Pi](https://pi.dev) coding agent 0.79 or newer
|
|
226
237
|
- A [Nerd Font](https://www.nerdfonts.com/) for icons
|
|
227
238
|
|
|
228
239
|
## Development
|
|
@@ -18,6 +18,16 @@ export type UiFeaturesConfig = {
|
|
|
18
18
|
copyFriendly: boolean;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
export type FooterSegmentsConfig = {
|
|
22
|
+
cwd: boolean;
|
|
23
|
+
gitBranch: boolean;
|
|
24
|
+
gitStatus: boolean;
|
|
25
|
+
runtime: boolean;
|
|
26
|
+
context: boolean;
|
|
27
|
+
tokens: boolean;
|
|
28
|
+
cost: boolean;
|
|
29
|
+
};
|
|
30
|
+
|
|
21
31
|
export type ExtensionStatusPlacement = "off" | "left" | "middle" | "right";
|
|
22
32
|
export type ExtensionStatusColorMode = "zentui" | "original";
|
|
23
33
|
|
|
@@ -77,6 +87,7 @@ export type PolishedTuiConfig = {
|
|
|
77
87
|
};
|
|
78
88
|
colorSources: ColorSourcesConfig;
|
|
79
89
|
features: UiFeaturesConfig;
|
|
90
|
+
footerSegments: FooterSegmentsConfig;
|
|
80
91
|
extensionStatuses: ExtensionStatusesConfig;
|
|
81
92
|
};
|
|
82
93
|
|
|
@@ -124,6 +135,15 @@ export const defaultConfig: PolishedTuiConfig = {
|
|
|
124
135
|
statusLine: true,
|
|
125
136
|
copyFriendly: false,
|
|
126
137
|
},
|
|
138
|
+
footerSegments: {
|
|
139
|
+
cwd: true,
|
|
140
|
+
gitBranch: true,
|
|
141
|
+
gitStatus: true,
|
|
142
|
+
runtime: true,
|
|
143
|
+
context: true,
|
|
144
|
+
tokens: true,
|
|
145
|
+
cost: true,
|
|
146
|
+
},
|
|
127
147
|
extensionStatuses: {
|
|
128
148
|
defaultPlacement: "right",
|
|
129
149
|
placements: {},
|
|
@@ -190,6 +210,14 @@ function booleanValue(record: Record<string, unknown>, key: keyof UiFeaturesConf
|
|
|
190
210
|
return typeof value === "boolean" ? value : defaultConfig.features[key];
|
|
191
211
|
}
|
|
192
212
|
|
|
213
|
+
function footerSegmentValue(
|
|
214
|
+
record: Record<string, unknown>,
|
|
215
|
+
key: keyof FooterSegmentsConfig,
|
|
216
|
+
): boolean {
|
|
217
|
+
const value = record[key];
|
|
218
|
+
return typeof value === "boolean" ? value : defaultConfig.footerSegments[key];
|
|
219
|
+
}
|
|
220
|
+
|
|
193
221
|
function definedColors(
|
|
194
222
|
colors: Partial<Record<keyof PolishedTuiConfig["colors"], string | undefined>>,
|
|
195
223
|
): Partial<PolishedTuiConfig["colors"]> {
|
|
@@ -252,6 +280,18 @@ function normalizeUiFeatures(record: Record<string, unknown>): UiFeaturesConfig
|
|
|
252
280
|
};
|
|
253
281
|
}
|
|
254
282
|
|
|
283
|
+
function normalizeFooterSegments(record: Record<string, unknown>): FooterSegmentsConfig {
|
|
284
|
+
return {
|
|
285
|
+
cwd: footerSegmentValue(record, "cwd"),
|
|
286
|
+
gitBranch: footerSegmentValue(record, "gitBranch"),
|
|
287
|
+
gitStatus: footerSegmentValue(record, "gitStatus"),
|
|
288
|
+
runtime: footerSegmentValue(record, "runtime"),
|
|
289
|
+
context: footerSegmentValue(record, "context"),
|
|
290
|
+
tokens: footerSegmentValue(record, "tokens"),
|
|
291
|
+
cost: footerSegmentValue(record, "cost"),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
255
295
|
export function isExtensionStatusPlacement(value: unknown): value is ExtensionStatusPlacement {
|
|
256
296
|
return value === "off" || value === "left" || value === "middle" || value === "right";
|
|
257
297
|
}
|
|
@@ -296,6 +336,18 @@ function isUiFeatureKey(value: string): value is keyof UiFeaturesConfig {
|
|
|
296
336
|
return value === "editor" || value === "statusLine" || value === "copyFriendly";
|
|
297
337
|
}
|
|
298
338
|
|
|
339
|
+
function isFooterSegmentKey(value: string): value is keyof FooterSegmentsConfig {
|
|
340
|
+
return (
|
|
341
|
+
value === "cwd" ||
|
|
342
|
+
value === "gitBranch" ||
|
|
343
|
+
value === "gitStatus" ||
|
|
344
|
+
value === "runtime" ||
|
|
345
|
+
value === "context" ||
|
|
346
|
+
value === "tokens" ||
|
|
347
|
+
value === "cost"
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
299
351
|
function validColorSourceEntries(record: Record<string, unknown>): Partial<ColorSourcesConfig> {
|
|
300
352
|
return Object.fromEntries(
|
|
301
353
|
Object.entries(record).filter((entry): entry is [keyof ColorSourcesConfig, ColorSource] => {
|
|
@@ -314,6 +366,15 @@ function validUiFeatureEntries(record: Record<string, unknown>): Partial<UiFeatu
|
|
|
314
366
|
) as Partial<UiFeaturesConfig>;
|
|
315
367
|
}
|
|
316
368
|
|
|
369
|
+
function validFooterSegmentEntries(record: Record<string, unknown>): Partial<FooterSegmentsConfig> {
|
|
370
|
+
return Object.fromEntries(
|
|
371
|
+
Object.entries(record).filter((entry): entry is [keyof FooterSegmentsConfig, boolean] => {
|
|
372
|
+
const [key, value] = entry;
|
|
373
|
+
return isFooterSegmentKey(key) && typeof value === "boolean";
|
|
374
|
+
}),
|
|
375
|
+
) as Partial<FooterSegmentsConfig>;
|
|
376
|
+
}
|
|
377
|
+
|
|
317
378
|
function readConfigRecord(path = configPath): ConfigRecord {
|
|
318
379
|
try {
|
|
319
380
|
if (!existsSync(path)) return {};
|
|
@@ -345,6 +406,9 @@ export function mergeConfig(parsed: unknown): PolishedTuiConfig {
|
|
|
345
406
|
const features = isRecord(config.features)
|
|
346
407
|
? normalizeUiFeatures(config.features as Record<string, unknown>)
|
|
347
408
|
: defaultConfig.features;
|
|
409
|
+
const footerSegments = isRecord(config.footerSegments)
|
|
410
|
+
? normalizeFooterSegments(config.footerSegments as Record<string, unknown>)
|
|
411
|
+
: defaultConfig.footerSegments;
|
|
348
412
|
const extensionStatuses = isRecord(config.extensionStatuses)
|
|
349
413
|
? normalizeExtensionStatuses(config.extensionStatuses as Record<string, unknown>)
|
|
350
414
|
: defaultConfig.extensionStatuses;
|
|
@@ -360,6 +424,7 @@ export function mergeConfig(parsed: unknown): PolishedTuiConfig {
|
|
|
360
424
|
},
|
|
361
425
|
colorSources: { ...colorSources },
|
|
362
426
|
features: { ...features },
|
|
427
|
+
footerSegments: { ...footerSegments },
|
|
363
428
|
extensionStatuses: {
|
|
364
429
|
defaultPlacement: extensionStatuses.defaultPlacement,
|
|
365
430
|
placements: { ...extensionStatuses.placements },
|
|
@@ -423,6 +488,22 @@ export function saveUiFeaturesPatch(
|
|
|
423
488
|
return mergeConfig(record);
|
|
424
489
|
}
|
|
425
490
|
|
|
491
|
+
export function saveFooterSegmentsPatch(
|
|
492
|
+
patch: Partial<FooterSegmentsConfig>,
|
|
493
|
+
path = configPath,
|
|
494
|
+
): PolishedTuiConfig {
|
|
495
|
+
const record = readConfigRecord(path);
|
|
496
|
+
const existing = isRecord(record.footerSegments)
|
|
497
|
+
? { ...(record.footerSegments as Record<string, unknown>) }
|
|
498
|
+
: {};
|
|
499
|
+
record.footerSegments = {
|
|
500
|
+
...existing,
|
|
501
|
+
...validFooterSegmentEntries(patch),
|
|
502
|
+
};
|
|
503
|
+
writeFileSync(path, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
504
|
+
return mergeConfig(record);
|
|
505
|
+
}
|
|
506
|
+
|
|
426
507
|
export function saveExtensionStatusPlacement(
|
|
427
508
|
key: string,
|
|
428
509
|
placement: ExtensionStatusPlacement,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
3
|
import type { PolishedTuiConfig } from "./config";
|
|
4
|
-
import { type ExtensionStatusSegment
|
|
4
|
+
import { collectExtensionStatusSegments, type ExtensionStatusSegment } from "./extension-status";
|
|
5
5
|
import { formatCwdLabel, formatRuntimeSegment } from "./format";
|
|
6
6
|
import type { FooterState } from "./state";
|
|
7
7
|
import { renderStyleForSource } from "./style";
|
|
@@ -196,24 +196,32 @@ export function installFooter(
|
|
|
196
196
|
: "";
|
|
197
197
|
const statusBlock =
|
|
198
198
|
allStatus || aheadBehind ? gitStatusColor(`[${allStatus}${aheadBehind}]`) : "";
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
199
|
+
const branchParts =
|
|
200
|
+
config.footerSegments.gitBranch && branch
|
|
201
|
+
? ["on", gitIcon, gitColor(branch)].filter(Boolean)
|
|
202
|
+
: [];
|
|
203
|
+
const gitStatusParts = config.footerSegments.gitStatus && statusBlock ? [statusBlock] : [];
|
|
204
|
+
const branchLabel = [...branchParts, ...gitStatusParts].filter(Boolean).join(" ");
|
|
205
|
+
const runtimeLabel = config.footerSegments.runtime
|
|
206
|
+
? formatRuntimeSegment(theme, state.runtime, config.colors.runtimePrefix, colorSource)
|
|
203
207
|
: "";
|
|
204
|
-
const runtimeLabel = formatRuntimeSegment(
|
|
205
|
-
theme,
|
|
206
|
-
state.runtime,
|
|
207
|
-
config.colors.runtimePrefix,
|
|
208
|
-
colorSource,
|
|
209
|
-
);
|
|
210
208
|
|
|
211
|
-
const left = [cwdLabel, branchLabel, runtimeLabel]
|
|
209
|
+
const left = [config.footerSegments.cwd ? cwdLabel : "", branchLabel, runtimeLabel]
|
|
210
|
+
.filter(Boolean)
|
|
211
|
+
.join(" ");
|
|
212
212
|
const right = [
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
213
|
+
config.footerSegments.context
|
|
214
|
+
? renderStyleForSource(theme, colorSource, contextColor, state.contextLabel)
|
|
215
|
+
: "",
|
|
216
|
+
config.footerSegments.tokens
|
|
217
|
+
? renderStyleForSource(theme, colorSource, config.colors.tokens, state.tokenLabel)
|
|
218
|
+
: "",
|
|
219
|
+
config.footerSegments.cost
|
|
220
|
+
? renderStyleForSource(theme, colorSource, config.colors.cost, state.costLabel)
|
|
221
|
+
: "",
|
|
222
|
+
]
|
|
223
|
+
.filter(Boolean)
|
|
224
|
+
.join(separator);
|
|
217
225
|
const extensionStatuses = collectExtensionStatusSegments(
|
|
218
226
|
footerData.getExtensionStatuses(),
|
|
219
227
|
config,
|
|
@@ -9,27 +9,29 @@ import {
|
|
|
9
9
|
type ColorSourcesConfig,
|
|
10
10
|
type ExtensionStatusColorMode,
|
|
11
11
|
type ExtensionStatusPlacement,
|
|
12
|
-
type PolishedTuiConfig,
|
|
13
|
-
type UiFeaturesConfig,
|
|
14
12
|
ensureConfigExists,
|
|
13
|
+
type FooterSegmentsConfig,
|
|
15
14
|
loadConfig,
|
|
15
|
+
type PolishedTuiConfig,
|
|
16
16
|
saveColorSourcesPatch,
|
|
17
17
|
saveExtensionStatusColorMode,
|
|
18
18
|
saveExtensionStatusPlacement,
|
|
19
|
+
saveFooterSegmentsPatch,
|
|
19
20
|
saveUiFeaturesPatch,
|
|
21
|
+
type UiFeaturesConfig,
|
|
20
22
|
} from "./config";
|
|
21
23
|
import { installFooter } from "./footer";
|
|
22
24
|
import { emptyGitStatus, readGitStatus } from "./git";
|
|
23
25
|
import {
|
|
26
|
+
createProjectRefreshScheduler,
|
|
24
27
|
type ScheduleProjectRefreshOptions,
|
|
25
28
|
type StopProjectRefreshInterval,
|
|
26
|
-
createProjectRefreshScheduler,
|
|
27
29
|
startProjectRefreshInterval,
|
|
28
30
|
} from "./project-refresh";
|
|
29
31
|
import { readRuntimeInfo } from "./runtime";
|
|
30
32
|
import { installSelectorBorderStyle } from "./selector-border";
|
|
31
33
|
import { registerZentuiSettingsCommand } from "./settings-command";
|
|
32
|
-
import { type FooterState,
|
|
34
|
+
import { createInitialState, type FooterState, syncState } from "./state";
|
|
33
35
|
import { PolishedEditor, WrappedPolishedEditor } from "./ui";
|
|
34
36
|
import { installUserMessageStyle } from "./user-message";
|
|
35
37
|
|
|
@@ -58,8 +60,12 @@ function getZentuiEditorBaseFactory(factory: EditorFactory | undefined): EditorF
|
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
function isTuiContext(ctx: ExtensionContext): boolean {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
try {
|
|
64
|
+
const mode = (ctx as ExtensionContext & { mode?: string }).mode;
|
|
65
|
+
return ctx.hasUI && (mode === undefined || mode === "tui");
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
export default function (pi: ExtensionAPI) {
|
|
@@ -341,6 +347,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
341
347
|
: undefined,
|
|
342
348
|
};
|
|
343
349
|
},
|
|
350
|
+
setFooterSegments(patch: Partial<FooterSegmentsConfig>) {
|
|
351
|
+
currentConfig = saveFooterSegmentsPatch(patch);
|
|
352
|
+
},
|
|
344
353
|
getActiveExtensionStatuses() {
|
|
345
354
|
return getActiveExtensionStatuses();
|
|
346
355
|
},
|
|
@@ -2,6 +2,8 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
|
|
|
2
2
|
import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import {
|
|
4
4
|
type AutocompleteItem,
|
|
5
|
+
Key,
|
|
6
|
+
matchesKey,
|
|
5
7
|
type SettingItem,
|
|
6
8
|
SettingsList,
|
|
7
9
|
type SettingsListTheme,
|
|
@@ -12,12 +14,13 @@ import {
|
|
|
12
14
|
type ColorSourcesConfig,
|
|
13
15
|
type ExtensionStatusColorMode,
|
|
14
16
|
type ExtensionStatusPlacement,
|
|
15
|
-
type
|
|
16
|
-
type UiFeaturesConfig,
|
|
17
|
+
type FooterSegmentsConfig,
|
|
17
18
|
getExtensionStatusColorMode,
|
|
18
19
|
getExtensionStatusPlacement,
|
|
19
20
|
isExtensionStatusColorMode,
|
|
20
21
|
isExtensionStatusPlacement,
|
|
22
|
+
type PolishedTuiConfig,
|
|
23
|
+
type UiFeaturesConfig,
|
|
21
24
|
} from "./config";
|
|
22
25
|
import { sanitizeExtensionStatusText } from "./extension-status";
|
|
23
26
|
import { EDITOR_BORDER_STYLE, renderChromeBorder, safeThemeFg } from "./style";
|
|
@@ -33,10 +36,11 @@ const extensionStatusColorModeValues: ExtensionStatusColorMode[] = ["zentui", "o
|
|
|
33
36
|
type FeatureState = "enabled" | "disabled";
|
|
34
37
|
|
|
35
38
|
const featureStateValues: FeatureState[] = ["enabled", "disabled"];
|
|
36
|
-
const settingsSections = ["coloring", "features", "
|
|
39
|
+
const settingsSections = ["coloring", "features", "builtinSegments", "extensionSegments"] as const;
|
|
37
40
|
|
|
38
41
|
type ColorSettingId = "starship" | "editorMessages";
|
|
39
42
|
type FeatureSettingId = keyof UiFeaturesConfig;
|
|
43
|
+
type FooterSegmentSettingId = keyof FooterSegmentsConfig;
|
|
40
44
|
type SettingsSection = (typeof settingsSections)[number];
|
|
41
45
|
|
|
42
46
|
type SettingsCommandDeps = {
|
|
@@ -46,6 +50,7 @@ type SettingsCommandDeps = {
|
|
|
46
50
|
patch: Partial<UiFeaturesConfig>,
|
|
47
51
|
ctx: ExtensionContext,
|
|
48
52
|
) => { applied: boolean; reason?: string };
|
|
53
|
+
setFooterSegments: (patch: Partial<FooterSegmentsConfig>) => void;
|
|
49
54
|
getActiveExtensionStatuses: () => ReadonlyMap<string, string>;
|
|
50
55
|
setExtensionStatusPlacement: (key: string, placement: ExtensionStatusPlacement) => void;
|
|
51
56
|
setExtensionStatusColorMode: (key: string, colorMode: ExtensionStatusColorMode) => void;
|
|
@@ -79,6 +84,26 @@ const featureSettingDescriptions: Record<FeatureSettingId, string> = {
|
|
|
79
84
|
"Hide editor and previous-message rail glyphs for cleaner native terminal selection.",
|
|
80
85
|
};
|
|
81
86
|
|
|
87
|
+
const footerSegmentSettingLabels: Record<FooterSegmentSettingId, string> = {
|
|
88
|
+
cwd: "Current directory",
|
|
89
|
+
gitBranch: "Git branch",
|
|
90
|
+
gitStatus: "Git status",
|
|
91
|
+
runtime: "Runtime",
|
|
92
|
+
context: "Context usage",
|
|
93
|
+
tokens: "Token counts",
|
|
94
|
+
cost: "Session cost",
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const footerSegmentSettingDescriptions: Record<FooterSegmentSettingId, string> = {
|
|
98
|
+
cwd: "Show or hide the current working directory segment on the left.",
|
|
99
|
+
gitBranch: "Show or hide the git branch name on the left.",
|
|
100
|
+
gitStatus: "Show or hide git status icons and ahead/behind markers.",
|
|
101
|
+
runtime: "Show or hide the detected runtime/language segment on the left.",
|
|
102
|
+
context: "Show or hide context usage on the right.",
|
|
103
|
+
tokens: "Show or hide input/output token counts on the right.",
|
|
104
|
+
cost: "Show or hide session cost on the right.",
|
|
105
|
+
};
|
|
106
|
+
|
|
82
107
|
const directCommandSuggestions = [
|
|
83
108
|
"editor enable",
|
|
84
109
|
"editor disable",
|
|
@@ -94,10 +119,12 @@ const directCommandSuggestions = [
|
|
|
94
119
|
const sectionLabels: Record<SettingsSection, string> = {
|
|
95
120
|
coloring: "Coloring",
|
|
96
121
|
features: "Features",
|
|
97
|
-
|
|
122
|
+
builtinSegments: "Built-in segments",
|
|
123
|
+
extensionSegments: "Extension segments",
|
|
98
124
|
};
|
|
99
125
|
|
|
100
126
|
const thirdPartyStatusSettingPrefix = "thirdPartyStatus:";
|
|
127
|
+
const footerSegmentSettingPrefix = "footerSegment:";
|
|
101
128
|
type ThirdPartyStatusSettingKind = "placement" | "colorMode";
|
|
102
129
|
|
|
103
130
|
function isColorSource(value: string): value is ColorSource {
|
|
@@ -112,6 +139,18 @@ function isFeatureSettingId(value: string): value is FeatureSettingId {
|
|
|
112
139
|
return value === "editor" || value === "statusLine" || value === "copyFriendly";
|
|
113
140
|
}
|
|
114
141
|
|
|
142
|
+
function isFooterSegmentSettingId(value: string): value is FooterSegmentSettingId {
|
|
143
|
+
return (
|
|
144
|
+
value === "cwd" ||
|
|
145
|
+
value === "gitBranch" ||
|
|
146
|
+
value === "gitStatus" ||
|
|
147
|
+
value === "runtime" ||
|
|
148
|
+
value === "context" ||
|
|
149
|
+
value === "tokens" ||
|
|
150
|
+
value === "cost"
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
115
154
|
function isFeatureState(value: string): value is FeatureState {
|
|
116
155
|
return value === "enabled" || value === "disabled";
|
|
117
156
|
}
|
|
@@ -134,6 +173,23 @@ function featurePatch(id: FeatureSettingId, value: FeatureState): Partial<UiFeat
|
|
|
134
173
|
return { [id]: value === "enabled" } as Partial<UiFeaturesConfig>;
|
|
135
174
|
}
|
|
136
175
|
|
|
176
|
+
function footerSegmentSettingId(key: FooterSegmentSettingId): string {
|
|
177
|
+
return `${footerSegmentSettingPrefix}${key}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function footerSegmentSettingFromId(id: string): FooterSegmentSettingId | undefined {
|
|
181
|
+
if (!id.startsWith(footerSegmentSettingPrefix)) return undefined;
|
|
182
|
+
const key = id.slice(footerSegmentSettingPrefix.length);
|
|
183
|
+
return isFooterSegmentSettingId(key) ? key : undefined;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function footerSegmentPatch(
|
|
187
|
+
id: FooterSegmentSettingId,
|
|
188
|
+
value: FeatureState,
|
|
189
|
+
): Partial<FooterSegmentsConfig> {
|
|
190
|
+
return { [id]: value === "enabled" } as Partial<FooterSegmentsConfig>;
|
|
191
|
+
}
|
|
192
|
+
|
|
137
193
|
function usageText(): string {
|
|
138
194
|
return "Usage: /zentui [editor|statusline|copy-friendly] [enable|disable|toggle]";
|
|
139
195
|
}
|
|
@@ -229,6 +285,16 @@ function buildItems(
|
|
|
229
285
|
}));
|
|
230
286
|
}
|
|
231
287
|
|
|
288
|
+
if (section === "builtinSegments") {
|
|
289
|
+
return (Object.keys(footerSegmentSettingLabels) as FooterSegmentSettingId[]).map((key) => ({
|
|
290
|
+
id: footerSegmentSettingId(key),
|
|
291
|
+
label: footerSegmentSettingLabels[key],
|
|
292
|
+
description: footerSegmentSettingDescriptions[key],
|
|
293
|
+
currentValue: featureValue(config.footerSegments[key]),
|
|
294
|
+
values: featureStateValues,
|
|
295
|
+
}));
|
|
296
|
+
}
|
|
297
|
+
|
|
232
298
|
const statuses = Array.from(activeStatuses.entries()).sort(([a], [b]) =>
|
|
233
299
|
a < b ? -1 : a > b ? 1 : 0,
|
|
234
300
|
);
|
|
@@ -237,8 +303,7 @@ function buildItems(
|
|
|
237
303
|
{
|
|
238
304
|
id: "noThirdPartyStatuses",
|
|
239
305
|
label: "No active statuses",
|
|
240
|
-
description:
|
|
241
|
-
"This section only lists statuses currently published through ctx.ui.setStatus().",
|
|
306
|
+
description: "This tab only lists statuses currently published through ctx.ui.setStatus().",
|
|
242
307
|
currentValue: "—",
|
|
243
308
|
},
|
|
244
309
|
];
|
|
@@ -271,6 +336,14 @@ function nextSection(section: SettingsSection): SettingsSection {
|
|
|
271
336
|
return settingsSections[(currentIndex + 1) % settingsSections.length] ?? "coloring";
|
|
272
337
|
}
|
|
273
338
|
|
|
339
|
+
function previousSection(section: SettingsSection): SettingsSection {
|
|
340
|
+
const currentIndex = settingsSections.indexOf(section);
|
|
341
|
+
return (
|
|
342
|
+
settingsSections[(currentIndex - 1 + settingsSections.length) % settingsSections.length] ??
|
|
343
|
+
"coloring"
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
274
347
|
function formatSectionTabs(
|
|
275
348
|
activeSection: SettingsSection,
|
|
276
349
|
theme: ExtensionContext["ui"]["theme"],
|
|
@@ -289,7 +362,7 @@ function withSectionFooter(lines: string[], theme: ExtensionContext["ui"]["theme
|
|
|
289
362
|
next[index] = safeThemeFg(
|
|
290
363
|
theme,
|
|
291
364
|
"muted",
|
|
292
|
-
" Enter/Space to change · Tab to switch sections · Esc to close",
|
|
365
|
+
" Enter/Space to change · Tab/Shift+Tab to switch sections · Esc to close",
|
|
293
366
|
);
|
|
294
367
|
break;
|
|
295
368
|
}
|
|
@@ -384,6 +457,19 @@ export function registerZentuiSettingsCommand(pi: ExtensionAPI, deps: SettingsCo
|
|
|
384
457
|
return;
|
|
385
458
|
}
|
|
386
459
|
|
|
460
|
+
const footerSegmentSetting = footerSegmentSettingFromId(id);
|
|
461
|
+
if (footerSegmentSetting && isFeatureState(newValue)) {
|
|
462
|
+
deps.setFooterSegments(footerSegmentPatch(footerSegmentSetting, newValue));
|
|
463
|
+
settingsList.updateValue(id, newValue);
|
|
464
|
+
deps.requestRender();
|
|
465
|
+
ctx.ui.notify(
|
|
466
|
+
`${footerSegmentSettingLabels[footerSegmentSetting]}: ${newValue}`,
|
|
467
|
+
"info",
|
|
468
|
+
);
|
|
469
|
+
tui.requestRender();
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
387
473
|
const thirdPartyStatusSetting = thirdPartyStatusSettingFromId(id);
|
|
388
474
|
if (
|
|
389
475
|
thirdPartyStatusSetting?.kind === "placement" &&
|
|
@@ -421,8 +507,9 @@ export function registerZentuiSettingsCommand(pi: ExtensionAPI, deps: SettingsCo
|
|
|
421
507
|
() => done(undefined),
|
|
422
508
|
);
|
|
423
509
|
settingsList = makeSettingsList();
|
|
424
|
-
const switchSection = () => {
|
|
425
|
-
activeSection =
|
|
510
|
+
const switchSection = (direction: "forward" | "backward") => {
|
|
511
|
+
activeSection =
|
|
512
|
+
direction === "forward" ? nextSection(activeSection) : previousSection(activeSection);
|
|
426
513
|
settingsList = makeSettingsList();
|
|
427
514
|
tui.requestRender();
|
|
428
515
|
};
|
|
@@ -450,8 +537,12 @@ export function registerZentuiSettingsCommand(pi: ExtensionAPI, deps: SettingsCo
|
|
|
450
537
|
settingsList.invalidate();
|
|
451
538
|
},
|
|
452
539
|
handleInput(data: string) {
|
|
453
|
-
if (data
|
|
454
|
-
switchSection();
|
|
540
|
+
if (matchesKey(data, Key.tab)) {
|
|
541
|
+
switchSection("forward");
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (matchesKey(data, Key.shift("tab"))) {
|
|
545
|
+
switchSection("backward");
|
|
455
546
|
return;
|
|
456
547
|
}
|
|
457
548
|
settingsList.handleInput(data);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-zentui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "A Starship-inspired statusline and Opencode-style TUI for Pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -12,8 +12,16 @@
|
|
|
12
12
|
"bugs": {
|
|
13
13
|
"url": "https://github.com/lmilojevicc/pi-zentui/issues"
|
|
14
14
|
},
|
|
15
|
-
"keywords": [
|
|
16
|
-
|
|
15
|
+
"keywords": [
|
|
16
|
+
"pi-package",
|
|
17
|
+
"pi-extension",
|
|
18
|
+
"pi-theme",
|
|
19
|
+
"starship",
|
|
20
|
+
"tui"
|
|
21
|
+
],
|
|
22
|
+
"files": [
|
|
23
|
+
"extensions"
|
|
24
|
+
],
|
|
17
25
|
"scripts": {
|
|
18
26
|
"lint": "biome check .",
|
|
19
27
|
"typecheck": "tsc --noEmit",
|
|
@@ -32,18 +40,20 @@
|
|
|
32
40
|
"@earendil-works/pi-tui": "*"
|
|
33
41
|
},
|
|
34
42
|
"pi": {
|
|
35
|
-
"extensions": [
|
|
43
|
+
"extensions": [
|
|
44
|
+
"./extensions"
|
|
45
|
+
],
|
|
36
46
|
"image": "https://raw.githubusercontent.com/lmilojevicc/pi-zentui/main/assets/zentui.png"
|
|
37
47
|
},
|
|
38
48
|
"publishConfig": {
|
|
39
49
|
"access": "public"
|
|
40
50
|
},
|
|
41
51
|
"devDependencies": {
|
|
42
|
-
"@biomejs/biome": "^
|
|
43
|
-
"@earendil-works/pi-ai": "^0.
|
|
44
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
45
|
-
"@earendil-works/pi-tui": "^0.
|
|
46
|
-
"@types/node": "^
|
|
52
|
+
"@biomejs/biome": "^2.5.0",
|
|
53
|
+
"@earendil-works/pi-ai": "^0.79.3",
|
|
54
|
+
"@earendil-works/pi-coding-agent": "^0.79.3",
|
|
55
|
+
"@earendil-works/pi-tui": "^0.79.3",
|
|
56
|
+
"@types/node": "^26.0.0",
|
|
47
57
|
"typescript": "^6.0.2",
|
|
48
58
|
"vitest": "^4.1.8"
|
|
49
59
|
}
|