pi-zentui 0.5.0 → 0.6.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
CHANGED
|
@@ -17,7 +17,7 @@ Zentui brings two popular aesthetics to Pi:
|
|
|
17
17
|
|
|
18
18
|
### Footer (Starship-inspired)
|
|
19
19
|
|
|
20
|
-
- ` dirname` — current directory with icon
|
|
20
|
+
- ` dirname` — current directory with icon (`basename` by default; optional `full` path with directory depth via `pathDisplay`)
|
|
21
21
|
- `on branch` — git branch with icon
|
|
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
|
|
@@ -158,6 +158,10 @@ Default config values — copy this and change any value you want:
|
|
|
158
158
|
"warning": 70,
|
|
159
159
|
"error": 90
|
|
160
160
|
},
|
|
161
|
+
"pathDisplay": {
|
|
162
|
+
"mode": "basename",
|
|
163
|
+
"depth": 0
|
|
164
|
+
},
|
|
161
165
|
"icons": {
|
|
162
166
|
"mode": "auto",
|
|
163
167
|
"cwd": "",
|
|
@@ -244,11 +248,12 @@ Default config values — copy this and change any value you want:
|
|
|
244
248
|
- `projectRefreshIntervalMs`: project status polling interval; `0` disables polling. Values `1..4999` clamp up to `5000` (minimum 5s); invalid/non-finite values fall back to `30000`.
|
|
245
249
|
- `contextStyle`: `text` (default), `gauge`, or `text+gauge` for the context segment.
|
|
246
250
|
- `contextThresholds`: `{ warning, error }` percentages (default `70` / `90`) that select contextNormal / contextWarning / contextError colors.
|
|
251
|
+
- `pathDisplay`: controls how the cwd/`$cwd` path is shown. `mode` is `basename` (default, last segment only) or `full` (path with home contracted to `~`). In `full` mode, `depth` keeps only the last N trailing directories (`0` = entire path after `~`, max `5`); when parents are dropped the path is prefixed with `…/` (Starship-style). The `/zentui` **Layout** tab cycles path mode and path depth (`0`–`5`; depth is ignored for basename). Example: `~/Projects/foo/bar` with `depth: 2` → `…/foo/bar`.
|
|
247
252
|
- `icons`: every shown icon key is configurable; omit any key to use the Zentui default. `icons.mode` is `auto` | `nerd` | `ascii` (default `auto`, same glyphs as nerd). ASCII mode swaps in plain fallbacks for statusline icons and runtime symbols — useful without a Nerd Font. Custom per-icon strings always win over mode defaults. Custom `icons.os` always wins; when left at the mode default, Zentui maps the OS icon by platform. `rail` sets the vertical glyph drawn as the left rail of the active editor frame and previous user messages when `copyFriendly` is disabled (default `│`; any single Unicode vertical or block glyph). `editorPrompt` controls an optional copy-friendly editor prompt glyph; the default is `""` so copy-friendly mode stays rail-free.
|
|
248
253
|
- `colorSources`: `theme` maps styles through Pi theme tokens; `terminal` emits terminal colors. `/zentui` switches these sources; manual JSON controls specific style values.
|
|
249
254
|
- `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.
|
|
250
255
|
- `footerSegments`: show or hide individual built-in footer segments (`cwd`, `gitBranch`, `gitStatus`, `gitCounts`, `runtime`, `sessionDuration`, `username`, `time`, `os`, `context`, `tokens`, `cost`). Toggle them from the `Built-in segments` tab in `/zentui`.
|
|
251
|
-
- `footerFormat`: optional Starship-style template string that fully controls the footer layout. When set, it overrides `footerSegments`. See [Footer Format Template](#footer-format-template) below. The `/zentui` **Layout** tab configures context style and icon mode; set or clear custom formats with `/zentui format`.
|
|
256
|
+
- `footerFormat`: optional Starship-style template string that fully controls the footer layout. When set, it overrides `footerSegments`. See [Footer Format Template](#footer-format-template) below. The `/zentui` **Layout** tab configures context style, path display mode/depth, and icon mode; set or clear custom formats with `/zentui format`.
|
|
252
257
|
- `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.
|
|
253
258
|
- 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`.
|
|
254
259
|
- `editorAccent` styles the active editor rail and previous user-message rail when `features.copyFriendly` is disabled.
|
|
@@ -23,6 +23,14 @@ export type ContextThresholds = {
|
|
|
23
23
|
error: number;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
export type PathDisplayMode = "basename" | "full";
|
|
27
|
+
|
|
28
|
+
export type PathDisplayConfig = {
|
|
29
|
+
mode: PathDisplayMode;
|
|
30
|
+
/** Trailing directories to show in full mode. 0 = unlimited; clamped to 0..5. */
|
|
31
|
+
depth: number;
|
|
32
|
+
};
|
|
33
|
+
|
|
26
34
|
export type ColorSourcesConfig = {
|
|
27
35
|
starship: ColorSource;
|
|
28
36
|
editor: ColorSource;
|
|
@@ -69,6 +77,7 @@ export type PolishedTuiConfig = {
|
|
|
69
77
|
footerFormat: string;
|
|
70
78
|
contextStyle: ContextStyle;
|
|
71
79
|
contextThresholds: ContextThresholds;
|
|
80
|
+
pathDisplay: PathDisplayConfig;
|
|
72
81
|
icons: ResolvedIcons;
|
|
73
82
|
colors: {
|
|
74
83
|
cwd: ColorSpec;
|
|
@@ -144,6 +153,7 @@ export const defaultConfig: PolishedTuiConfig = {
|
|
|
144
153
|
footerFormat: "",
|
|
145
154
|
contextStyle: "text",
|
|
146
155
|
contextThresholds: { warning: 70, error: 90 },
|
|
156
|
+
pathDisplay: { mode: "basename", depth: 0 },
|
|
147
157
|
icons: {
|
|
148
158
|
mode: "auto",
|
|
149
159
|
...NERD_DEFAULT_ICONS,
|
|
@@ -244,6 +254,18 @@ function parseContextThresholds(value: unknown): ContextThresholds {
|
|
|
244
254
|
return { warning, error };
|
|
245
255
|
}
|
|
246
256
|
|
|
257
|
+
function parsePathDisplay(value: unknown): PathDisplayConfig {
|
|
258
|
+
const defaults = defaultConfig.pathDisplay;
|
|
259
|
+
if (!isRecord(value)) return { ...defaults };
|
|
260
|
+
const mode = value.mode === "full" || value.mode === "basename" ? value.mode : defaults.mode;
|
|
261
|
+
const rawDepth = value.depth;
|
|
262
|
+
const depth =
|
|
263
|
+
typeof rawDepth === "number" && Number.isFinite(rawDepth) && rawDepth >= 0
|
|
264
|
+
? Math.min(5, Math.floor(rawDepth))
|
|
265
|
+
: defaults.depth;
|
|
266
|
+
return { mode, depth };
|
|
267
|
+
}
|
|
268
|
+
|
|
247
269
|
function stringValue(record: Record<string, unknown>, key: string): string | undefined {
|
|
248
270
|
const value = record[key];
|
|
249
271
|
return typeof value === "string" ? value : undefined;
|
|
@@ -488,6 +510,7 @@ export function mergeConfig(parsed: unknown): PolishedTuiConfig {
|
|
|
488
510
|
footerFormat: stringValue(config, "footerFormat") ?? "",
|
|
489
511
|
contextStyle: parseContextStyle(config.contextStyle),
|
|
490
512
|
contextThresholds: parseContextThresholds(config.contextThresholds),
|
|
513
|
+
pathDisplay: parsePathDisplay(config.pathDisplay),
|
|
491
514
|
icons: resolveConfiguredIcons(iconMode, iconOverrides),
|
|
492
515
|
colors: {
|
|
493
516
|
...defaultConfig.colors,
|
|
@@ -616,6 +639,21 @@ export function saveContextThresholdsPatch(
|
|
|
616
639
|
return mergeConfig(record);
|
|
617
640
|
}
|
|
618
641
|
|
|
642
|
+
export function savePathDisplayPatch(
|
|
643
|
+
patch: Partial<PathDisplayConfig>,
|
|
644
|
+
path = configPath,
|
|
645
|
+
): PolishedTuiConfig {
|
|
646
|
+
const record = readConfigRecord(path);
|
|
647
|
+
const existing = isRecord(record.pathDisplay)
|
|
648
|
+
? { ...(record.pathDisplay as Record<string, unknown>) }
|
|
649
|
+
: {};
|
|
650
|
+
if (patch.mode !== undefined) existing.mode = patch.mode;
|
|
651
|
+
if (patch.depth !== undefined) existing.depth = patch.depth;
|
|
652
|
+
record.pathDisplay = existing;
|
|
653
|
+
writeFileSync(path, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
654
|
+
return mergeConfig(record);
|
|
655
|
+
}
|
|
656
|
+
|
|
619
657
|
export function saveExtensionStatusPlacement(
|
|
620
658
|
key: string,
|
|
621
659
|
placement: ExtensionStatusPlacement,
|
|
@@ -172,7 +172,10 @@ export function installFooter(
|
|
|
172
172
|
theme,
|
|
173
173
|
colorSource,
|
|
174
174
|
config.colors.cwd,
|
|
175
|
-
formatCwdLabel(ctx.cwd, config.icons.cwd
|
|
175
|
+
formatCwdLabel(ctx.cwd, config.icons.cwd, {
|
|
176
|
+
mode: config.pathDisplay.mode,
|
|
177
|
+
depth: config.pathDisplay.depth,
|
|
178
|
+
}),
|
|
176
179
|
);
|
|
177
180
|
const branch = state.branch;
|
|
178
181
|
const contextUsage = ctx.getContextUsage();
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import { hostname, userInfo } from "node:os";
|
|
1
|
+
import { homedir, hostname, userInfo } from "node:os";
|
|
2
2
|
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
3
3
|
import type { ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
ColorSource,
|
|
6
|
+
ColorSpec,
|
|
7
|
+
ContextStyle,
|
|
8
|
+
ContextThresholds,
|
|
9
|
+
PathDisplayMode,
|
|
10
|
+
} from "./config";
|
|
5
11
|
import type { IconMode } from "./icons";
|
|
6
12
|
import { resolveOsIcon, resolveRuntimeSymbol } from "./icons";
|
|
7
13
|
import type { RuntimeInfo } from "./runtime";
|
|
@@ -238,11 +244,72 @@ export function formatRuntimeSegment(
|
|
|
238
244
|
return `${renderStyleForSource(theme, colorSource, prefixStyle, "via")} ${renderStyleForSource(theme, colorSource, runtime.style, label)}`;
|
|
239
245
|
}
|
|
240
246
|
|
|
241
|
-
export
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
247
|
+
export type FormatCwdOptions = {
|
|
248
|
+
mode?: PathDisplayMode;
|
|
249
|
+
/** Trailing directory components to keep in full mode. 0 = unlimited. */
|
|
250
|
+
depth?: number;
|
|
251
|
+
home?: string;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
function normalizeDisplayPath(cwd: string): string {
|
|
255
|
+
const withSlashes = cwd.replace(/\\/g, "/");
|
|
256
|
+
if (withSlashes === "/" || /^\/+$/.test(withSlashes)) return "/";
|
|
257
|
+
const stripped = withSlashes.replace(/\/+$/, "");
|
|
258
|
+
return stripped === "" ? withSlashes : stripped;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function toHomePath(path: string, home: string): string {
|
|
262
|
+
if (!home) return path;
|
|
263
|
+
const homeNorm = home.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
264
|
+
if (!homeNorm) return path;
|
|
265
|
+
if (path === homeNorm) return "~";
|
|
266
|
+
if (path.startsWith(`${homeNorm}/`)) return `~${path.slice(homeNorm.length)}`;
|
|
267
|
+
return path;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Starship-style: keep last `depth` components; prefix with `…/` when parents were dropped. */
|
|
271
|
+
function applyPathDepth(path: string, depth: number): string {
|
|
272
|
+
if (!Number.isFinite(depth) || depth <= 0) return path;
|
|
273
|
+
const limit = Math.floor(depth);
|
|
274
|
+
if (path === "~" || path === "/") return path;
|
|
275
|
+
|
|
276
|
+
let components: string[];
|
|
277
|
+
if (path.startsWith("~/")) {
|
|
278
|
+
components = path.slice(2).split("/").filter(Boolean);
|
|
279
|
+
} else if (/^[A-Za-z]:\//.test(path)) {
|
|
280
|
+
components = path.slice(3).split("/").filter(Boolean);
|
|
281
|
+
} else if (path.startsWith("/")) {
|
|
282
|
+
components = path.slice(1).split("/").filter(Boolean);
|
|
283
|
+
} else {
|
|
284
|
+
components = path.split("/").filter(Boolean);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (components.length <= limit) return path;
|
|
288
|
+
return `…/${components.slice(-limit).join("/")}`;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function formatCwdLabel(cwd: string, cwdIcon: string, options?: FormatCwdOptions): string {
|
|
292
|
+
const mode = options?.mode ?? "basename";
|
|
293
|
+
const normalized = normalizeDisplayPath(cwd);
|
|
294
|
+
let pathText: string;
|
|
295
|
+
if (mode === "full") {
|
|
296
|
+
const home =
|
|
297
|
+
options?.home ??
|
|
298
|
+
(() => {
|
|
299
|
+
try {
|
|
300
|
+
return homedir();
|
|
301
|
+
} catch {
|
|
302
|
+
return "";
|
|
303
|
+
}
|
|
304
|
+
})();
|
|
305
|
+
pathText = applyPathDepth(toHomePath(normalized, home), options?.depth ?? 0);
|
|
306
|
+
} else if (normalized === "/") {
|
|
307
|
+
pathText = "/";
|
|
308
|
+
} else {
|
|
309
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
310
|
+
pathText = parts[parts.length - 1] ?? cwd;
|
|
311
|
+
}
|
|
312
|
+
return cwdIcon ? `${cwdIcon} ${pathText}` : pathText;
|
|
246
313
|
}
|
|
247
314
|
|
|
248
315
|
export function formatUsernameHostLabel(icon: string): string {
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type FooterSegmentsConfig,
|
|
15
15
|
type IconMode,
|
|
16
16
|
loadConfig,
|
|
17
|
+
type PathDisplayConfig,
|
|
17
18
|
type PolishedTuiConfig,
|
|
18
19
|
saveColorSourcesPatch,
|
|
19
20
|
saveContextStylePatch,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
saveFooterFormatPatch,
|
|
23
24
|
saveFooterSegmentsPatch,
|
|
24
25
|
saveIconsModePatch,
|
|
26
|
+
savePathDisplayPatch,
|
|
25
27
|
saveUiFeaturesPatch,
|
|
26
28
|
type UiFeaturesConfig,
|
|
27
29
|
} from "./config";
|
|
@@ -408,6 +410,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
408
410
|
setContextStyle(style: ContextStyle) {
|
|
409
411
|
currentConfig = saveContextStylePatch(style);
|
|
410
412
|
},
|
|
413
|
+
setPathDisplay(patch: Partial<PathDisplayConfig>) {
|
|
414
|
+
currentConfig = savePathDisplayPatch(patch);
|
|
415
|
+
},
|
|
411
416
|
getActiveExtensionStatuses() {
|
|
412
417
|
return getActiveExtensionStatuses();
|
|
413
418
|
},
|
|
@@ -21,6 +21,8 @@ import {
|
|
|
21
21
|
type IconMode,
|
|
22
22
|
isExtensionStatusColorMode,
|
|
23
23
|
isExtensionStatusPlacement,
|
|
24
|
+
type PathDisplayConfig,
|
|
25
|
+
type PathDisplayMode,
|
|
24
26
|
type PolishedTuiConfig,
|
|
25
27
|
type UiFeaturesConfig,
|
|
26
28
|
} from "./config";
|
|
@@ -37,6 +39,8 @@ const extensionStatusPlacementValues: ExtensionStatusPlacement[] = [
|
|
|
37
39
|
];
|
|
38
40
|
const extensionStatusColorModeValues: ExtensionStatusColorMode[] = ["zentui", "original"];
|
|
39
41
|
const contextStyleValues: ContextStyle[] = ["text", "gauge", "text+gauge"];
|
|
42
|
+
const pathDisplayModeValues: PathDisplayMode[] = ["basename", "full"];
|
|
43
|
+
const pathDepthValues = ["0", "1", "2", "3", "4", "5"] as const;
|
|
40
44
|
const iconModeValues: IconMode[] = ["auto", "nerd", "ascii"];
|
|
41
45
|
type FeatureState = "enabled" | "disabled";
|
|
42
46
|
|
|
@@ -53,7 +57,7 @@ type ColorSettingId = "starship" | "editorMessages";
|
|
|
53
57
|
type FeatureSettingId = keyof UiFeaturesConfig;
|
|
54
58
|
type FooterSegmentSettingId = keyof FooterSegmentsConfig;
|
|
55
59
|
type SettingsSection = (typeof settingsSections)[number];
|
|
56
|
-
type LayoutSettingId = "contextStyle" | "iconMode";
|
|
60
|
+
type LayoutSettingId = "contextStyle" | "pathDisplay" | "pathDepth" | "iconMode";
|
|
57
61
|
|
|
58
62
|
type SettingsCommandDeps = {
|
|
59
63
|
getConfig: () => PolishedTuiConfig;
|
|
@@ -66,6 +70,7 @@ type SettingsCommandDeps = {
|
|
|
66
70
|
setFooterFormat: (value: string) => void;
|
|
67
71
|
setIconMode: (mode: IconMode) => void;
|
|
68
72
|
setContextStyle: (style: ContextStyle) => void;
|
|
73
|
+
setPathDisplay: (patch: Partial<PathDisplayConfig>) => void;
|
|
69
74
|
getActiveExtensionStatuses: () => ReadonlyMap<string, string>;
|
|
70
75
|
setExtensionStatusPlacement: (key: string, placement: ExtensionStatusPlacement) => void;
|
|
71
76
|
setExtensionStatusColorMode: (key: string, colorMode: ExtensionStatusColorMode) => void;
|
|
@@ -194,8 +199,21 @@ function isContextStyle(value: string): value is ContextStyle {
|
|
|
194
199
|
return value === "text" || value === "gauge" || value === "text+gauge";
|
|
195
200
|
}
|
|
196
201
|
|
|
202
|
+
function isPathDisplayMode(value: string): value is PathDisplayMode {
|
|
203
|
+
return value === "basename" || value === "full";
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function isPathDepthValue(value: string): boolean {
|
|
207
|
+
return (pathDepthValues as readonly string[]).includes(value);
|
|
208
|
+
}
|
|
209
|
+
|
|
197
210
|
function isLayoutSettingId(value: string): value is LayoutSettingId {
|
|
198
|
-
return
|
|
211
|
+
return (
|
|
212
|
+
value === "contextStyle" ||
|
|
213
|
+
value === "pathDisplay" ||
|
|
214
|
+
value === "pathDepth" ||
|
|
215
|
+
value === "iconMode"
|
|
216
|
+
);
|
|
199
217
|
}
|
|
200
218
|
|
|
201
219
|
function editorMessageValue(config: PolishedTuiConfig): ColorSource | "mixed" {
|
|
@@ -349,6 +367,21 @@ function buildItems(
|
|
|
349
367
|
currentValue: config.contextStyle,
|
|
350
368
|
values: contextStyleValues,
|
|
351
369
|
},
|
|
370
|
+
{
|
|
371
|
+
id: "pathDisplay",
|
|
372
|
+
label: "Path display",
|
|
373
|
+
description: "Show cwd as basename or full path (home contracted to ~).",
|
|
374
|
+
currentValue: config.pathDisplay.mode,
|
|
375
|
+
values: pathDisplayModeValues,
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
id: "pathDepth",
|
|
379
|
+
label: "Path depth",
|
|
380
|
+
description:
|
|
381
|
+
"In full mode, trailing directories to show (0 = all, max 5). Ignored for basename.",
|
|
382
|
+
currentValue: String(config.pathDisplay.depth),
|
|
383
|
+
values: [...pathDepthValues],
|
|
384
|
+
},
|
|
352
385
|
{
|
|
353
386
|
id: "iconMode",
|
|
354
387
|
label: "Icon mode",
|
|
@@ -561,6 +594,24 @@ export function registerZentuiSettingsCommand(pi: ExtensionAPI, deps: SettingsCo
|
|
|
561
594
|
return;
|
|
562
595
|
}
|
|
563
596
|
|
|
597
|
+
if (id === "pathDisplay" && isPathDisplayMode(newValue)) {
|
|
598
|
+
deps.setPathDisplay({ mode: newValue });
|
|
599
|
+
settingsList.updateValue(id, newValue);
|
|
600
|
+
deps.requestRender();
|
|
601
|
+
ctx.ui.notify(`Path display: ${newValue}`, "info");
|
|
602
|
+
tui.requestRender();
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
if (id === "pathDepth" && isPathDepthValue(newValue)) {
|
|
607
|
+
deps.setPathDisplay({ depth: Number(newValue) });
|
|
608
|
+
settingsList.updateValue(id, newValue);
|
|
609
|
+
deps.requestRender();
|
|
610
|
+
ctx.ui.notify(`Path depth: ${newValue}`, "info");
|
|
611
|
+
tui.requestRender();
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
|
|
564
615
|
if (id === "iconMode" && isIconMode(newValue)) {
|
|
565
616
|
deps.setIconMode(newValue);
|
|
566
617
|
settingsList.updateValue(id, newValue);
|