pi-zentui 0.4.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 +39 -23
- package/extensions/zentui/config.ts +140 -76
- package/extensions/zentui/footer-format.ts +184 -26
- package/extensions/zentui/footer.ts +53 -21
- package/extensions/zentui/format.ts +206 -27
- package/extensions/zentui/git.ts +140 -4
- package/extensions/zentui/icons.ts +225 -0
- package/extensions/zentui/index.ts +35 -10
- package/extensions/zentui/project-state.ts +44 -0
- package/extensions/zentui/runtime.ts +64 -11
- package/extensions/zentui/settings-command.ts +115 -1
- package/extensions/zentui/style.ts +13 -2
- package/package.json +1 -1
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
|
|
@@ -129,7 +129,7 @@ pi install git:github.com/lmilojevicc/pi-zentui
|
|
|
129
129
|
|
|
130
130
|
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.
|
|
131
131
|
|
|
132
|
-
The interactive `/zentui` menu is split into
|
|
132
|
+
The interactive `/zentui` menu is split into five sections. Use `Tab` and `Shift+Tab` to switch between `Coloring`, `Features`, `Layout`, `Built-in segments`, and `Extension segments`.
|
|
133
133
|
|
|
134
134
|
Useful slash-command shortcuts:
|
|
135
135
|
|
|
@@ -153,7 +153,17 @@ Default config values — copy this and change any value you want:
|
|
|
153
153
|
{
|
|
154
154
|
"projectRefreshIntervalMs": 30000,
|
|
155
155
|
"footerFormat": "",
|
|
156
|
+
"contextStyle": "text",
|
|
157
|
+
"contextThresholds": {
|
|
158
|
+
"warning": 70,
|
|
159
|
+
"error": 90
|
|
160
|
+
},
|
|
161
|
+
"pathDisplay": {
|
|
162
|
+
"mode": "basename",
|
|
163
|
+
"depth": 0
|
|
164
|
+
},
|
|
156
165
|
"icons": {
|
|
166
|
+
"mode": "auto",
|
|
157
167
|
"cwd": "",
|
|
158
168
|
"git": "",
|
|
159
169
|
"ahead": "↑",
|
|
@@ -234,13 +244,16 @@ Default config values — copy this and change any value you want:
|
|
|
234
244
|
}
|
|
235
245
|
```
|
|
236
246
|
|
|
237
|
-
- Style values can be Starship/terminal strings (`bold purple`, `fg:202`, `#89b4fa`, `bg:blue fg:bright-green`) or Pi theme tokens (`accent`, `borderMuted`, `thinkingHigh`).
|
|
238
|
-
- `projectRefreshIntervalMs`: project status polling interval; `0` disables polling.
|
|
239
|
-
- `
|
|
247
|
+
- Style values can be Starship/terminal strings (`bold purple`, `fg:202`, `#89b` / `#89b4fa`, `bg:blue fg:bright-green`) or Pi theme tokens (`accent`, `borderMuted`, `thinkingHigh`). Short `#rgb` hex values expand to `#rrggbb`.
|
|
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`.
|
|
249
|
+
- `contextStyle`: `text` (default), `gauge`, or `text+gauge` for the context segment.
|
|
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`.
|
|
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.
|
|
240
253
|
- `colorSources`: `theme` maps styles through Pi theme tokens; `terminal` emits terminal colors. `/zentui` switches these sources; manual JSON controls specific style values.
|
|
241
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.
|
|
242
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`.
|
|
243
|
-
- `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.
|
|
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`.
|
|
244
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.
|
|
245
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`.
|
|
246
259
|
- `editorAccent` styles the active editor rail and previous user-message rail when `features.copyFriendly` is disabled.
|
|
@@ -252,13 +265,13 @@ Tip: when using copy-friendly mode, setting Pi's `editorPaddingX` to `1` in `~/.
|
|
|
252
265
|
|
|
253
266
|
## Footer Format Template
|
|
254
267
|
|
|
255
|
-
For full control, set a Starship-style `footerFormat` template string. It supports `$variable` and `${variable}` tokens
|
|
268
|
+
For full control, set a Starship-style `footerFormat` template string. It supports `$variable` and `${variable}` tokens, a special `$fill` token that splits the line into left and right zones, and conditional groups `( ... )` that drop entirely when every nested variable is empty. When set, it overrides the built-in `footerSegments` layout; when empty or omitted, the segment layout above is used.
|
|
256
269
|
|
|
257
270
|
A second `$fill` creates a **centered middle zone** — content between the two fills is true-centered (`floor((gap - middle) / 2)`), just like third-party statuses placed `middle`.
|
|
258
271
|
|
|
259
272
|
```json
|
|
260
273
|
{
|
|
261
|
-
"footerFormat": "$os $username $cwd on
|
|
274
|
+
"footerFormat": "$os $username $cwd( on $git_branch)( $git_status)( via $runtime)$fill($context)($sep$tokens)($sep$cost)($sep$time)"
|
|
262
275
|
}
|
|
263
276
|
```
|
|
264
277
|
|
|
@@ -272,20 +285,22 @@ Center the branch between directory and cost:
|
|
|
272
285
|
|
|
273
286
|
### Variables
|
|
274
287
|
|
|
275
|
-
| Token | Aliases | Renders
|
|
276
|
-
| ------------------- | ------------ |
|
|
277
|
-
| `$cwd` | `$directory` | current directory
|
|
278
|
-
| `$git_branch` | `$branch` | git branch with icon
|
|
279
|
-
| `$git_status` | `$status` | `[!?↑]` status block
|
|
280
|
-
| `$
|
|
281
|
-
| `$
|
|
282
|
-
| `$
|
|
283
|
-
| `$
|
|
284
|
-
| `$
|
|
285
|
-
| `$
|
|
286
|
-
| `$
|
|
287
|
-
| `$
|
|
288
|
-
| `$
|
|
288
|
+
| Token | Aliases | Renders |
|
|
289
|
+
| ------------------- | ------------ | ------------------------------------------------ |
|
|
290
|
+
| `$cwd` | `$directory` | current directory |
|
|
291
|
+
| `$git_branch` | `$branch` | git branch with icon |
|
|
292
|
+
| `$git_status` | `$status` | `[!?↑]` status block |
|
|
293
|
+
| `$git_state` | `$state` | `REBASING` / `MERGING` / … (optional `n/m`) |
|
|
294
|
+
| `$runtime` | | runtime icon + version |
|
|
295
|
+
| `$session_duration` | `$duration` | session running time |
|
|
296
|
+
| `$username` | | `user@host` |
|
|
297
|
+
| `$os` | | operating-system icon |
|
|
298
|
+
| `$time` | | current time `HH:MM` |
|
|
299
|
+
| `$context` | | context usage (text and/or gauge via config) |
|
|
300
|
+
| `$tokens` | | input/output token counts |
|
|
301
|
+
| `$cost` | | session cost |
|
|
302
|
+
| `$sep` | `$separator` | themed ` | ` using `colors.separator` |
|
|
303
|
+
| `$fill` | — | special: splits zones |
|
|
289
304
|
|
|
290
305
|
### `$fill` behavior
|
|
291
306
|
|
|
@@ -298,13 +313,14 @@ Center the branch between directory and cost:
|
|
|
298
313
|
|
|
299
314
|
- Literal text (`on branch`, `using`, `\|`, spaces) is rendered verbatim — you control all spacing.
|
|
300
315
|
- Each variable renders its core value only (no `on`/`via` prefixes); add those words as literal text.
|
|
316
|
+
- Conditional groups: wrap optional pieces in parentheses, e.g. `$cwd( on $git_branch)($git_status)$fill($context)`. If every `$var` inside a group is empty, the whole group (including its literals) is dropped.
|
|
301
317
|
- Unknown `$variables` render empty.
|
|
302
318
|
- Set or clear at runtime: `/zentui format "<template>"` and `/zentui format clear`.
|
|
303
319
|
|
|
304
320
|
## Requirements
|
|
305
321
|
|
|
306
322
|
- [Pi](https://pi.dev) coding agent 0.79 or newer
|
|
307
|
-
- A [Nerd Font](https://www.nerdfonts.com/) for icons
|
|
323
|
+
- A [Nerd Font](https://www.nerdfonts.com/) for icons (or set `icons.mode` to `"ascii"`)
|
|
308
324
|
|
|
309
325
|
## Development
|
|
310
326
|
|
|
@@ -1,10 +1,35 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import {
|
|
5
|
+
ICON_GLYPH_KEYS,
|
|
6
|
+
type IconGlyphs,
|
|
7
|
+
type IconMode,
|
|
8
|
+
NERD_DEFAULT_ICONS,
|
|
9
|
+
normalizeIconMode,
|
|
10
|
+
type ResolvedIcons,
|
|
11
|
+
resolveConfiguredIcons,
|
|
12
|
+
} from "./icons";
|
|
4
13
|
import { isSupportedColorSpec } from "./style";
|
|
5
14
|
|
|
6
15
|
export type ColorSpec = string;
|
|
7
16
|
export type ColorSource = "theme" | "terminal";
|
|
17
|
+
export type { IconMode } from "./icons";
|
|
18
|
+
|
|
19
|
+
export type ContextStyle = "text" | "gauge" | "text+gauge";
|
|
20
|
+
|
|
21
|
+
export type ContextThresholds = {
|
|
22
|
+
warning: number;
|
|
23
|
+
error: number;
|
|
24
|
+
};
|
|
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
|
+
};
|
|
8
33
|
|
|
9
34
|
export type ColorSourcesConfig = {
|
|
10
35
|
starship: ColorSource;
|
|
@@ -50,27 +75,10 @@ const MIN_PROJECT_REFRESH_INTERVAL_MS = 5_000;
|
|
|
50
75
|
export type PolishedTuiConfig = {
|
|
51
76
|
projectRefreshIntervalMs: number;
|
|
52
77
|
footerFormat: string;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
behind: string;
|
|
58
|
-
diverged: string;
|
|
59
|
-
conflicted: string;
|
|
60
|
-
untracked: string;
|
|
61
|
-
stashed: string;
|
|
62
|
-
modified: string;
|
|
63
|
-
staged: string;
|
|
64
|
-
renamed: string;
|
|
65
|
-
deleted: string;
|
|
66
|
-
typechanged: string;
|
|
67
|
-
cacheHit: string;
|
|
68
|
-
editorPrompt: string;
|
|
69
|
-
rail: string;
|
|
70
|
-
username: string;
|
|
71
|
-
time: string;
|
|
72
|
-
os: string;
|
|
73
|
-
};
|
|
78
|
+
contextStyle: ContextStyle;
|
|
79
|
+
contextThresholds: ContextThresholds;
|
|
80
|
+
pathDisplay: PathDisplayConfig;
|
|
81
|
+
icons: ResolvedIcons;
|
|
74
82
|
colors: {
|
|
75
83
|
cwd: ColorSpec;
|
|
76
84
|
gitBranch: ColorSpec;
|
|
@@ -113,6 +121,7 @@ export const FOOTER_FORMAT_VARIABLES = [
|
|
|
113
121
|
"cwd",
|
|
114
122
|
"git_branch",
|
|
115
123
|
"git_status",
|
|
124
|
+
"git_state",
|
|
116
125
|
"runtime",
|
|
117
126
|
"session_duration",
|
|
118
127
|
"username",
|
|
@@ -121,6 +130,7 @@ export const FOOTER_FORMAT_VARIABLES = [
|
|
|
121
130
|
"context",
|
|
122
131
|
"tokens",
|
|
123
132
|
"cost",
|
|
133
|
+
"sep",
|
|
124
134
|
] as const;
|
|
125
135
|
|
|
126
136
|
/**
|
|
@@ -131,7 +141,9 @@ export const FOOTER_FORMAT_ALIASES: Record<string, string> = {
|
|
|
131
141
|
directory: "cwd",
|
|
132
142
|
branch: "git_branch",
|
|
133
143
|
status: "git_status",
|
|
144
|
+
state: "git_state",
|
|
134
145
|
duration: "session_duration",
|
|
146
|
+
separator: "sep",
|
|
135
147
|
};
|
|
136
148
|
|
|
137
149
|
export const configPath = join(getAgentDir(), "zentui.json");
|
|
@@ -139,26 +151,12 @@ export const configPath = join(getAgentDir(), "zentui.json");
|
|
|
139
151
|
export const defaultConfig: PolishedTuiConfig = {
|
|
140
152
|
projectRefreshIntervalMs: DEFAULT_PROJECT_REFRESH_INTERVAL_MS,
|
|
141
153
|
footerFormat: "",
|
|
154
|
+
contextStyle: "text",
|
|
155
|
+
contextThresholds: { warning: 70, error: 90 },
|
|
156
|
+
pathDisplay: { mode: "basename", depth: 0 },
|
|
142
157
|
icons: {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
ahead: "↑",
|
|
146
|
-
behind: "↓",
|
|
147
|
-
diverged: "⇕",
|
|
148
|
-
conflicted: "=",
|
|
149
|
-
untracked: "?",
|
|
150
|
-
stashed: "$",
|
|
151
|
-
modified: "!",
|
|
152
|
-
staged: "+",
|
|
153
|
-
renamed: "»",
|
|
154
|
-
deleted: "✘",
|
|
155
|
-
typechanged: "T",
|
|
156
|
-
cacheHit: "",
|
|
157
|
-
editorPrompt: "",
|
|
158
|
-
rail: "│",
|
|
159
|
-
username: "",
|
|
160
|
-
time: "",
|
|
161
|
-
os: "",
|
|
158
|
+
mode: "auto",
|
|
159
|
+
...NERD_DEFAULT_ICONS,
|
|
162
160
|
},
|
|
163
161
|
colors: {
|
|
164
162
|
cwd: "bold cyan",
|
|
@@ -208,27 +206,6 @@ export const defaultConfig: PolishedTuiConfig = {
|
|
|
208
206
|
},
|
|
209
207
|
};
|
|
210
208
|
|
|
211
|
-
const iconKeys = [
|
|
212
|
-
"cwd",
|
|
213
|
-
"git",
|
|
214
|
-
"ahead",
|
|
215
|
-
"behind",
|
|
216
|
-
"diverged",
|
|
217
|
-
"conflicted",
|
|
218
|
-
"untracked",
|
|
219
|
-
"stashed",
|
|
220
|
-
"modified",
|
|
221
|
-
"staged",
|
|
222
|
-
"renamed",
|
|
223
|
-
"deleted",
|
|
224
|
-
"typechanged",
|
|
225
|
-
"cacheHit",
|
|
226
|
-
"editorPrompt",
|
|
227
|
-
"username",
|
|
228
|
-
"time",
|
|
229
|
-
"os",
|
|
230
|
-
] as const satisfies readonly (keyof PolishedTuiConfig["icons"])[];
|
|
231
|
-
|
|
232
209
|
type ConfigRecord = Record<string, unknown>;
|
|
233
210
|
|
|
234
211
|
function isRecord(value: unknown): value is ConfigRecord {
|
|
@@ -238,17 +215,55 @@ function isRecord(value: unknown): value is ConfigRecord {
|
|
|
238
215
|
function parseProjectRefreshIntervalMs(value: unknown): number {
|
|
239
216
|
if (value === 0) return 0;
|
|
240
217
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
241
|
-
return
|
|
218
|
+
return DEFAULT_PROJECT_REFRESH_INTERVAL_MS;
|
|
242
219
|
}
|
|
243
220
|
|
|
244
221
|
const interval = Math.round(value);
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
: defaultConfig.projectRefreshIntervalMs;
|
|
222
|
+
if (interval <= 0) return 0;
|
|
223
|
+
return Math.max(MIN_PROJECT_REFRESH_INTERVAL_MS, interval);
|
|
248
224
|
}
|
|
249
225
|
|
|
250
|
-
function
|
|
251
|
-
return
|
|
226
|
+
function clampPercent(value: number): number {
|
|
227
|
+
return Math.max(0, Math.min(100, value));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function parseContextStyle(value: unknown): ContextStyle {
|
|
231
|
+
if (value === "text" || value === "gauge" || value === "text+gauge") return value;
|
|
232
|
+
return defaultConfig.contextStyle;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function parseContextThresholds(value: unknown): ContextThresholds {
|
|
236
|
+
const defaults = defaultConfig.contextThresholds;
|
|
237
|
+
if (!isRecord(value)) return { ...defaults };
|
|
238
|
+
|
|
239
|
+
const warningRaw = value.warning;
|
|
240
|
+
const errorRaw = value.error;
|
|
241
|
+
let warning =
|
|
242
|
+
typeof warningRaw === "number" && Number.isFinite(warningRaw)
|
|
243
|
+
? clampPercent(Math.round(warningRaw))
|
|
244
|
+
: defaults.warning;
|
|
245
|
+
let error =
|
|
246
|
+
typeof errorRaw === "number" && Number.isFinite(errorRaw)
|
|
247
|
+
? clampPercent(Math.round(errorRaw))
|
|
248
|
+
: defaults.error;
|
|
249
|
+
if (error < warning) {
|
|
250
|
+
const swapped = warning;
|
|
251
|
+
warning = error;
|
|
252
|
+
error = swapped;
|
|
253
|
+
}
|
|
254
|
+
return { warning, error };
|
|
255
|
+
}
|
|
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 };
|
|
252
267
|
}
|
|
253
268
|
|
|
254
269
|
function stringValue(record: Record<string, unknown>, key: string): string | undefined {
|
|
@@ -292,13 +307,13 @@ function definedColors(
|
|
|
292
307
|
) as Partial<PolishedTuiConfig["colors"]>;
|
|
293
308
|
}
|
|
294
309
|
|
|
295
|
-
function
|
|
310
|
+
function normalizeIconOverrides(record: Record<string, unknown>): Partial<IconGlyphs> {
|
|
296
311
|
return Object.fromEntries(
|
|
297
|
-
|
|
312
|
+
ICON_GLYPH_KEYS.flatMap((key) => {
|
|
298
313
|
const value = stringValue(record, key);
|
|
299
314
|
return value === undefined ? [] : [[key, value]];
|
|
300
315
|
}),
|
|
301
|
-
) as Partial<
|
|
316
|
+
) as Partial<IconGlyphs>;
|
|
302
317
|
}
|
|
303
318
|
|
|
304
319
|
function normalizeColors(record: Record<string, unknown>): Partial<PolishedTuiConfig["colors"]> {
|
|
@@ -473,7 +488,8 @@ export function ensureConfigExists(): void {
|
|
|
473
488
|
export function mergeConfig(parsed: unknown): PolishedTuiConfig {
|
|
474
489
|
const config = isRecord(parsed) ? parsed : {};
|
|
475
490
|
const iconsRecord = isRecord(config.icons) ? (config.icons as Record<string, unknown>) : {};
|
|
476
|
-
const
|
|
491
|
+
const iconMode = normalizeIconMode(iconsRecord.mode);
|
|
492
|
+
const iconOverrides = normalizeIconOverrides(iconsRecord);
|
|
477
493
|
const colors = isRecord(config.colors)
|
|
478
494
|
? normalizeColors(config.colors as Record<string, unknown>)
|
|
479
495
|
: {};
|
|
@@ -492,11 +508,10 @@ export function mergeConfig(parsed: unknown): PolishedTuiConfig {
|
|
|
492
508
|
return {
|
|
493
509
|
projectRefreshIntervalMs: parseProjectRefreshIntervalMs(config.projectRefreshIntervalMs),
|
|
494
510
|
footerFormat: stringValue(config, "footerFormat") ?? "",
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
},
|
|
511
|
+
contextStyle: parseContextStyle(config.contextStyle),
|
|
512
|
+
contextThresholds: parseContextThresholds(config.contextThresholds),
|
|
513
|
+
pathDisplay: parsePathDisplay(config.pathDisplay),
|
|
514
|
+
icons: resolveConfiguredIcons(iconMode, iconOverrides),
|
|
500
515
|
colors: {
|
|
501
516
|
...defaultConfig.colors,
|
|
502
517
|
...colors,
|
|
@@ -590,6 +605,55 @@ export function saveFooterFormatPatch(value: string, path = configPath): Polishe
|
|
|
590
605
|
return mergeConfig(record);
|
|
591
606
|
}
|
|
592
607
|
|
|
608
|
+
export function saveIconsModePatch(mode: IconMode, path = configPath): PolishedTuiConfig {
|
|
609
|
+
const record = readConfigRecord(path);
|
|
610
|
+
const existing = isRecord(record.icons) ? { ...(record.icons as Record<string, unknown>) } : {};
|
|
611
|
+
record.icons = {
|
|
612
|
+
...existing,
|
|
613
|
+
mode: normalizeIconMode(mode),
|
|
614
|
+
};
|
|
615
|
+
writeFileSync(path, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
616
|
+
return mergeConfig(record);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
export function saveContextStylePatch(style: ContextStyle, path = configPath): PolishedTuiConfig {
|
|
620
|
+
const record = readConfigRecord(path);
|
|
621
|
+
record.contextStyle = parseContextStyle(style);
|
|
622
|
+
writeFileSync(path, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
623
|
+
return mergeConfig(record);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export function saveContextThresholdsPatch(
|
|
627
|
+
thresholds: Partial<ContextThresholds>,
|
|
628
|
+
path = configPath,
|
|
629
|
+
): PolishedTuiConfig {
|
|
630
|
+
const record = readConfigRecord(path);
|
|
631
|
+
const existing = isRecord(record.contextThresholds)
|
|
632
|
+
? { ...(record.contextThresholds as Record<string, unknown>) }
|
|
633
|
+
: {};
|
|
634
|
+
record.contextThresholds = {
|
|
635
|
+
...existing,
|
|
636
|
+
...thresholds,
|
|
637
|
+
};
|
|
638
|
+
writeFileSync(path, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
639
|
+
return mergeConfig(record);
|
|
640
|
+
}
|
|
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
|
+
|
|
593
657
|
export function saveExtensionStatusPlacement(
|
|
594
658
|
key: string,
|
|
595
659
|
placement: ExtensionStatusPlacement,
|