@xzzpig/pi-starline 0.5.1 → 0.7.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/FORK-CHANGELOG.md +19 -0
- package/README.md +68 -181
- package/extensions/starline/config.ts +156 -326
- package/extensions/starline/context-live.ts +33 -0
- package/extensions/starline/editor-cursor.ts +3 -0
- package/extensions/starline/extension-status.ts +9 -5
- package/extensions/starline/footer.ts +331 -528
- package/extensions/starline/fork-editor-cursor.ts +14 -0
- package/extensions/starline/format.ts +151 -20
- package/extensions/starline/git-live.ts +20 -0
- package/extensions/starline/git-probe.ts +40 -0
- package/extensions/starline/git-segments.ts +175 -0
- package/extensions/starline/git.ts +71 -0
- package/extensions/starline/icons.ts +17 -0
- package/extensions/starline/index.ts +188 -91
- package/extensions/starline/live-session.ts +164 -0
- package/extensions/starline/live-tps.ts +165 -0
- package/extensions/starline/mouse/bubble-dismiss.ts +82 -0
- package/extensions/starline/mouse/cursor-marker.ts +69 -0
- package/extensions/starline/mouse/editor-hit-test.ts +6 -3
- package/extensions/starline/mouse/external-layer.ts +42 -0
- package/extensions/starline/mouse/fork-native-clipboard.ts +57 -0
- package/extensions/starline/mouse/fork-pill-mouse.ts +19 -0
- package/extensions/starline/mouse/fork-pointer-coordination.ts +10 -0
- package/extensions/starline/mouse/fork-transcript-copy.ts +54 -0
- package/extensions/starline/mouse/fork-wheel-compat.ts +22 -0
- package/extensions/starline/mouse/hint.ts +4 -4
- package/extensions/starline/mouse/index.ts +54 -24
- package/extensions/starline/mouse/pointer-events.ts +73 -0
- package/extensions/starline/mouse/pointer-shape.ts +138 -0
- package/extensions/starline/mouse/transcript-copy.ts +76 -33
- package/extensions/starline/paste-collapse.ts +52 -13
- package/extensions/starline/pill-actions.ts +590 -0
- package/extensions/starline/pill-anchors.ts +26 -0
- package/extensions/starline/pill-api.ts +80 -0
- package/extensions/starline/pill-bubble.ts +607 -0
- package/extensions/starline/pill-config.ts +67 -9
- package/extensions/starline/pill.ts +102 -20
- package/extensions/starline/project-probe.ts +79 -0
- package/extensions/starline/prototype-patch-registry.ts +6 -1
- package/extensions/starline/queue-rows.ts +43 -0
- package/extensions/starline/settings-command.ts +124 -177
- package/extensions/starline/subagent-fleet.ts +246 -0
- package/extensions/starline/turn-clock.ts +81 -0
- package/extensions/starline/ui.ts +297 -156
- package/extensions/starline/working-indicator.ts +189 -0
- package/package.json +6 -5
- package/types/pill-api.d.ts +115 -0
- package/extensions/starline/editor-metadata-format.ts +0 -259
- package/extensions/starline/footer-format.ts +0 -279
|
@@ -25,13 +25,7 @@ import {
|
|
|
25
25
|
type ResolvedIcons,
|
|
26
26
|
resolveConfiguredIcons,
|
|
27
27
|
} from "./icons";
|
|
28
|
-
import {
|
|
29
|
-
DEFAULT_PILL_CONFIG,
|
|
30
|
-
type FooterStyle,
|
|
31
|
-
normalizePillConfig,
|
|
32
|
-
type PillConfig,
|
|
33
|
-
parseFooterStyle,
|
|
34
|
-
} from "./pill-config";
|
|
28
|
+
import { DEFAULT_PILL_CONFIG, normalizePillConfig, type PillConfig } from "./pill-config";
|
|
35
29
|
import { isSupportedColorSpec } from "./style";
|
|
36
30
|
|
|
37
31
|
export type ColorSpec = string;
|
|
@@ -43,12 +37,14 @@ export type ContextStyle = "text" | "gauge" | "text+gauge";
|
|
|
43
37
|
export type ContextFormat = "full" | "percent";
|
|
44
38
|
/** What the tokens segment shows for cache activity. */
|
|
45
39
|
export type TokensCacheFormat = "percent" | "tokens" | "off";
|
|
40
|
+
/** What the subagents segment spells out: `2 agents`, or just the count. */
|
|
41
|
+
export type SubagentsFormat = "agents" | "count";
|
|
46
42
|
|
|
47
43
|
export type SegmentOptionsConfig = {
|
|
48
44
|
context: { format: ContextFormat };
|
|
49
45
|
tokens: { cache: TokensCacheFormat };
|
|
46
|
+
subagents: { format: SubagentsFormat };
|
|
50
47
|
};
|
|
51
|
-
export type SeparatorStyle = "pipe" | "dot" | "chevron" | "none";
|
|
52
48
|
export type ModelLabelSource = "id" | "name";
|
|
53
49
|
|
|
54
50
|
export type ContextThresholds = {
|
|
@@ -82,26 +78,14 @@ export type UiFeaturesConfig = {
|
|
|
82
78
|
copyFriendly: boolean;
|
|
83
79
|
};
|
|
84
80
|
|
|
85
|
-
export type
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
gitCommit: boolean;
|
|
94
|
-
gitMetrics: boolean;
|
|
95
|
-
runtime: boolean;
|
|
96
|
-
context: boolean;
|
|
97
|
-
tokens: boolean;
|
|
98
|
-
cacheHit: boolean;
|
|
99
|
-
cost: boolean;
|
|
100
|
-
sessionDuration: boolean;
|
|
101
|
-
username: boolean;
|
|
102
|
-
time: boolean;
|
|
103
|
-
os: boolean;
|
|
104
|
-
packageVersion: boolean;
|
|
81
|
+
export type GitConfig = {
|
|
82
|
+
/** Glyphs carry their number (`!3`) instead of standing alone (`!`). */
|
|
83
|
+
counts: boolean;
|
|
84
|
+
/** Use the origin remote's forge logo as the branch icon. */
|
|
85
|
+
hostIcon: boolean;
|
|
86
|
+
branch: GitBranchConfig;
|
|
87
|
+
commit: GitCommitConfig;
|
|
88
|
+
metrics: GitMetricsConfig;
|
|
105
89
|
};
|
|
106
90
|
|
|
107
91
|
export type MouseConfig = {
|
|
@@ -154,15 +138,16 @@ export function migrateFixedEditorKeys(raw: Record<string, unknown>): {
|
|
|
154
138
|
}
|
|
155
139
|
|
|
156
140
|
/**
|
|
157
|
-
* Where a third-party status is drawn.
|
|
158
|
-
*
|
|
159
|
-
* `editor` is the right-hand side of the editor's metadata row, alongside the
|
|
160
|
-
* vim-mode label and the copy/paste hints — not the footer. It needs Starline's
|
|
161
|
-
* own footer installed to have data at all, because the status map only reaches
|
|
162
|
-
* the extension through the footer factory.
|
|
141
|
+
* Where in the footer a third-party status is drawn, or `off`. Up to 0.8 there
|
|
142
|
+
* was also `editor`, the editor's metadata row; see `readExtensionStatusPlacement`.
|
|
163
143
|
*/
|
|
164
|
-
export type ExtensionStatusPlacement = "off" | "left" | "middle" | "right"
|
|
144
|
+
export type ExtensionStatusPlacement = "off" | "left" | "middle" | "right";
|
|
165
145
|
export type ExtensionStatusColorMode = "themed" | "original";
|
|
146
|
+
/**
|
|
147
|
+
* How much of a status is drawn: its text as published, or just its number.
|
|
148
|
+
* "value" is for statuses whose words are redundant once the pill has an icon.
|
|
149
|
+
*/
|
|
150
|
+
export type ExtensionStatusFormat = "full" | "value";
|
|
166
151
|
|
|
167
152
|
/**
|
|
168
153
|
* Starship `git_commit`-style options.
|
|
@@ -193,20 +178,20 @@ export type ExtensionStatusesConfig = {
|
|
|
193
178
|
colors: Record<string, ColorSpec>;
|
|
194
179
|
/** Per-status icon, keyed by status key. Prefixed to the status text. */
|
|
195
180
|
icons: Record<string, string>;
|
|
181
|
+
/**
|
|
182
|
+
* Per-status text shape, keyed by status key. `"value"` keeps only the number
|
|
183
|
+
* (`Tavily: 41.1%` → `41.1%`), so an icon plus that number reads as one pill;
|
|
184
|
+
* a status with no number keeps its text. Defaults to the published text.
|
|
185
|
+
*/
|
|
186
|
+
formats: Record<string, ExtensionStatusFormat>;
|
|
196
187
|
};
|
|
197
188
|
|
|
198
189
|
const DEFAULT_PROJECT_REFRESH_INTERVAL_MS = 30_000;
|
|
199
190
|
const MIN_PROJECT_REFRESH_INTERVAL_MS = 5_000;
|
|
200
|
-
export const DEFAULT_EDITOR_METADATA_FORMAT = "$model $provider( $thinking)";
|
|
201
191
|
|
|
202
192
|
export type PolishedTuiConfig = {
|
|
203
193
|
projectRefreshIntervalMs: number;
|
|
204
|
-
/** "text" renders footerFormat as before; "pill" renders pill.segments instead. */
|
|
205
|
-
footerStyle: FooterStyle;
|
|
206
194
|
pill: PillConfig;
|
|
207
|
-
footerFormat: string;
|
|
208
|
-
editorMetadataFormat: string;
|
|
209
|
-
separator: SeparatorStyle;
|
|
210
195
|
contextStyle: ContextStyle;
|
|
211
196
|
segmentOptions: SegmentOptionsConfig;
|
|
212
197
|
editorModelLabel: ModelLabelSource;
|
|
@@ -215,157 +200,103 @@ export type PolishedTuiConfig = {
|
|
|
215
200
|
editorClickCursor: boolean;
|
|
216
201
|
/** Collapse pastes at this many lines. 11 (default) leaves Pi's threshold alone. */
|
|
217
202
|
pasteCollapseLines: number;
|
|
218
|
-
/** Blank rows inside the editor box, above
|
|
203
|
+
/** Blank rows inside the editor box, above and below the input. */
|
|
219
204
|
editorPaddingY: number;
|
|
220
|
-
/**
|
|
205
|
+
/** The input area's height floor: a shorter draft is followed by blank rows. */
|
|
206
|
+
editorMinRows: number;
|
|
207
|
+
/** Blank rows above and below a Starline user message. */
|
|
221
208
|
userMessagePaddingY: number;
|
|
222
209
|
contextThresholds: ContextThresholds;
|
|
223
210
|
pathDisplay: PathDisplayConfig;
|
|
224
|
-
|
|
225
|
-
/** Replace the branch icon with the origin remote's forge logo. */
|
|
226
|
-
gitHostIcon: boolean;
|
|
211
|
+
git: GitConfig;
|
|
227
212
|
icons: ResolvedIcons;
|
|
228
213
|
colors: {
|
|
229
214
|
model: ColorSpec;
|
|
230
215
|
/** Unset derives the colour from the level via the theme's thinking* keys. */
|
|
231
216
|
thinking?: ColorSpec;
|
|
217
|
+
/** The queued-messages pill, which only exists while something is queued. */
|
|
218
|
+
queue: ColorSpec;
|
|
219
|
+
/** The running-subagents pill, which only exists while pi-subagents runs something. */
|
|
220
|
+
subagents: ColorSpec;
|
|
232
221
|
cwd: ColorSpec;
|
|
233
222
|
sessionName: ColorSpec;
|
|
234
223
|
gitBranch: ColorSpec;
|
|
235
|
-
gitStatus: ColorSpec;
|
|
236
224
|
contextNormal: ColorSpec;
|
|
237
225
|
contextWarning: ColorSpec;
|
|
238
226
|
contextError: ColorSpec;
|
|
239
|
-
tokens: ColorSpec;
|
|
240
|
-
cacheHit: ColorSpec;
|
|
241
227
|
cost: ColorSpec;
|
|
242
|
-
|
|
228
|
+
/** Decode throughput of the latest assistant message. */
|
|
229
|
+
tps: ColorSpec;
|
|
243
230
|
runtimePrefix: ColorSpec;
|
|
244
231
|
extensionStatus: ColorSpec;
|
|
245
232
|
sessionDuration: ColorSpec;
|
|
246
233
|
packageVersion: ColorSpec;
|
|
247
234
|
gitCommit: ColorSpec;
|
|
248
235
|
gitMetricsAdded: ColorSpec;
|
|
249
|
-
gitMetricsDeleted: ColorSpec;
|
|
250
236
|
username: ColorSpec;
|
|
251
237
|
time: ColorSpec;
|
|
252
238
|
os: ColorSpec;
|
|
253
239
|
editorAccent?: ColorSpec;
|
|
254
240
|
editorPrompt?: ColorSpec;
|
|
255
241
|
editorBorder?: ColorSpec;
|
|
256
|
-
/** Unset falls back to editorBorder, which is upstream's behaviour. */
|
|
257
242
|
userMessageBorder?: ColorSpec;
|
|
258
|
-
/** Unset follows the theme's userMessageText colour. */
|
|
259
243
|
userMessageText?: ColorSpec;
|
|
260
|
-
editorModel?: ColorSpec;
|
|
261
|
-
editorProvider?: ColorSpec;
|
|
262
|
-
editorThinking?: ColorSpec;
|
|
263
|
-
editorThinkingMinimal?: ColorSpec;
|
|
264
|
-
editorThinkingLow?: ColorSpec;
|
|
265
|
-
editorThinkingMedium?: ColorSpec;
|
|
266
|
-
editorThinkingHigh?: ColorSpec;
|
|
267
|
-
editorThinkingXhigh?: ColorSpec;
|
|
268
244
|
};
|
|
269
245
|
colorSources: ColorSourcesConfig;
|
|
270
246
|
features: UiFeaturesConfig;
|
|
271
|
-
footerSegments: FooterSegmentsConfig;
|
|
272
|
-
gitCommit: GitCommitConfig;
|
|
273
|
-
gitMetrics: GitMetricsConfig;
|
|
274
247
|
extensionStatuses: ExtensionStatusesConfig;
|
|
275
248
|
mouse: MouseConfig;
|
|
276
249
|
};
|
|
277
250
|
|
|
278
|
-
/**
|
|
279
|
-
* Canonical footer format variable names. In a `footerFormat` string these
|
|
280
|
-
* are written as `$name` or `${name}`.
|
|
281
|
-
*/
|
|
282
|
-
export const FOOTER_FORMAT_VARIABLES = [
|
|
283
|
-
"model",
|
|
284
|
-
"thinking",
|
|
285
|
-
"cwd",
|
|
286
|
-
"session_name",
|
|
287
|
-
"git_branch",
|
|
288
|
-
"git_status",
|
|
289
|
-
"git_state",
|
|
290
|
-
"runtime",
|
|
291
|
-
"session_duration",
|
|
292
|
-
"username",
|
|
293
|
-
"os",
|
|
294
|
-
"time",
|
|
295
|
-
"context",
|
|
296
|
-
"tokens",
|
|
297
|
-
"cache_hit",
|
|
298
|
-
"cost",
|
|
299
|
-
"package",
|
|
300
|
-
"package_version",
|
|
301
|
-
"git_commit",
|
|
302
|
-
"git_tag",
|
|
303
|
-
"git_metrics",
|
|
304
|
-
"git_added",
|
|
305
|
-
"git_deleted",
|
|
306
|
-
"sep",
|
|
307
|
-
] as const;
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Alias → canonical variable name mapping for `footerFormat`.
|
|
311
|
-
* `$fill` is special (not a variable) and handled by the parser.
|
|
312
|
-
*/
|
|
313
|
-
export const FOOTER_FORMAT_ALIASES: Record<string, string> = {
|
|
314
|
-
directory: "cwd",
|
|
315
|
-
branch: "git_branch",
|
|
316
|
-
status: "git_status",
|
|
317
|
-
state: "git_state",
|
|
318
|
-
commit: "git_commit",
|
|
319
|
-
tag: "git_tag",
|
|
320
|
-
duration: "session_duration",
|
|
321
|
-
separator: "sep",
|
|
322
|
-
};
|
|
323
|
-
|
|
324
251
|
export const configPath = join(getAgentDir(), "starline.json");
|
|
325
252
|
|
|
326
253
|
export const defaultConfig: PolishedTuiConfig = {
|
|
327
254
|
projectRefreshIntervalMs: DEFAULT_PROJECT_REFRESH_INTERVAL_MS,
|
|
328
|
-
footerStyle: "text",
|
|
329
255
|
pill: DEFAULT_PILL_CONFIG,
|
|
330
|
-
footerFormat: "",
|
|
331
|
-
editorMetadataFormat: DEFAULT_EDITOR_METADATA_FORMAT,
|
|
332
|
-
separator: "pipe",
|
|
333
256
|
contextStyle: "text",
|
|
334
|
-
segmentOptions: {
|
|
257
|
+
segmentOptions: {
|
|
258
|
+
context: { format: "full" },
|
|
259
|
+
tokens: { cache: "percent" },
|
|
260
|
+
subagents: { format: "agents" },
|
|
261
|
+
},
|
|
335
262
|
editorModelLabel: "id",
|
|
336
263
|
editorCursor: "block",
|
|
337
264
|
editorClickCursor: true,
|
|
338
265
|
pasteCollapseLines: 11,
|
|
339
266
|
editorPaddingY: 1,
|
|
267
|
+
editorMinRows: 1,
|
|
340
268
|
userMessagePaddingY: 1,
|
|
341
269
|
contextThresholds: { warning: 70, error: 90 },
|
|
342
270
|
pathDisplay: { mode: "basename", depth: 0 },
|
|
343
|
-
|
|
344
|
-
|
|
271
|
+
git: {
|
|
272
|
+
counts: false,
|
|
273
|
+
hostIcon: false,
|
|
274
|
+
branch: { maxLength: "full" },
|
|
275
|
+
commit: { hashLength: 7, onlyDetached: true, showTag: true },
|
|
276
|
+
metrics: { onlyNonzero: true, ignoreSubmodules: false },
|
|
277
|
+
},
|
|
345
278
|
icons: {
|
|
346
279
|
mode: "auto",
|
|
347
280
|
...NERD_DEFAULT_ICONS,
|
|
348
281
|
},
|
|
349
282
|
colors: {
|
|
350
283
|
model: "bold blue",
|
|
284
|
+
queue: "bold yellow",
|
|
285
|
+
subagents: "bold green",
|
|
351
286
|
cwd: "bold cyan",
|
|
352
287
|
sessionName: "bold green",
|
|
353
288
|
gitBranch: "bold purple",
|
|
354
|
-
gitStatus: "bold red",
|
|
355
289
|
contextNormal: "bright-black",
|
|
356
290
|
contextWarning: "bold yellow",
|
|
357
291
|
contextError: "bold red",
|
|
358
|
-
tokens: "bright-black",
|
|
359
|
-
cacheHit: "bright-black",
|
|
360
292
|
cost: "bold green",
|
|
361
|
-
|
|
293
|
+
tps: "bright-black",
|
|
362
294
|
runtimePrefix: "",
|
|
363
295
|
extensionStatus: "bright-black",
|
|
364
296
|
sessionDuration: "yellow",
|
|
365
297
|
packageVersion: "208",
|
|
366
298
|
gitCommit: "bold green",
|
|
367
299
|
gitMetricsAdded: "bold green",
|
|
368
|
-
gitMetricsDeleted: "bold red",
|
|
369
300
|
username: "bold yellow",
|
|
370
301
|
time: "bold yellow",
|
|
371
302
|
os: "bold white",
|
|
@@ -380,42 +311,13 @@ export const defaultConfig: PolishedTuiConfig = {
|
|
|
380
311
|
statusLine: true,
|
|
381
312
|
copyFriendly: false,
|
|
382
313
|
},
|
|
383
|
-
footerSegments: {
|
|
384
|
-
model: false,
|
|
385
|
-
thinking: false,
|
|
386
|
-
cwd: true,
|
|
387
|
-
sessionName: true,
|
|
388
|
-
gitBranch: true,
|
|
389
|
-
gitStatus: true,
|
|
390
|
-
gitCounts: false,
|
|
391
|
-
runtime: true,
|
|
392
|
-
context: true,
|
|
393
|
-
tokens: true,
|
|
394
|
-
cacheHit: false,
|
|
395
|
-
cost: true,
|
|
396
|
-
sessionDuration: false,
|
|
397
|
-
username: false,
|
|
398
|
-
time: false,
|
|
399
|
-
os: false,
|
|
400
|
-
packageVersion: false,
|
|
401
|
-
gitCommit: false,
|
|
402
|
-
gitMetrics: false,
|
|
403
|
-
},
|
|
404
|
-
gitCommit: {
|
|
405
|
-
hashLength: 7,
|
|
406
|
-
onlyDetached: true,
|
|
407
|
-
showTag: true,
|
|
408
|
-
},
|
|
409
|
-
gitMetrics: {
|
|
410
|
-
onlyNonzero: true,
|
|
411
|
-
ignoreSubmodules: false,
|
|
412
|
-
},
|
|
413
314
|
extensionStatuses: {
|
|
414
315
|
defaultPlacement: "right",
|
|
415
316
|
colors: {},
|
|
416
317
|
icons: {},
|
|
417
318
|
placements: {},
|
|
418
319
|
colorModes: {},
|
|
320
|
+
formats: {},
|
|
419
321
|
},
|
|
420
322
|
mouse: {
|
|
421
323
|
enabled: true,
|
|
@@ -457,14 +359,6 @@ function parseEditorModelLabel(value: unknown): ModelLabelSource {
|
|
|
457
359
|
return defaultConfig.editorModelLabel;
|
|
458
360
|
}
|
|
459
361
|
|
|
460
|
-
export function isSeparatorStyle(value: unknown): value is SeparatorStyle {
|
|
461
|
-
return value === "pipe" || value === "dot" || value === "chevron" || value === "none";
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
function parseSeparatorStyle(value: unknown): SeparatorStyle {
|
|
465
|
-
return isSeparatorStyle(value) ? value : defaultConfig.separator;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
362
|
function parseContextThresholds(value: unknown): ContextThresholds {
|
|
469
363
|
const defaults = defaultConfig.contextThresholds;
|
|
470
364
|
if (!isRecord(value)) return { ...defaults };
|
|
@@ -502,14 +396,21 @@ function parsePathDisplay(value: unknown): PathDisplayConfig {
|
|
|
502
396
|
function normalizeGitBranchMaxLength(value: unknown): GitBranchMaxLength {
|
|
503
397
|
if (value === "full") return value;
|
|
504
398
|
if (typeof value === "number" && Number.isInteger(value) && value > 0) return value;
|
|
505
|
-
return defaultConfig.
|
|
399
|
+
return defaultConfig.git.branch.maxLength;
|
|
506
400
|
}
|
|
507
401
|
|
|
508
|
-
function
|
|
509
|
-
const defaults = defaultConfig.
|
|
510
|
-
if (!isRecord(value)) return
|
|
402
|
+
function parseGitConfig(value: unknown): GitConfig {
|
|
403
|
+
const defaults = defaultConfig.git;
|
|
404
|
+
if (!isRecord(value)) return structuredClone(defaults);
|
|
405
|
+
const branch = isRecord(value.branch) ? value.branch : {};
|
|
406
|
+
const commit = isRecord(value.commit) ? value.commit : {};
|
|
407
|
+
const metrics = isRecord(value.metrics) ? value.metrics : {};
|
|
511
408
|
return {
|
|
512
|
-
|
|
409
|
+
counts: typeof value.counts === "boolean" ? value.counts : defaults.counts,
|
|
410
|
+
hostIcon: typeof value.hostIcon === "boolean" ? value.hostIcon : defaults.hostIcon,
|
|
411
|
+
branch: { maxLength: normalizeGitBranchMaxLength(branch.maxLength) },
|
|
412
|
+
commit: normalizeGitCommitConfig(commit),
|
|
413
|
+
metrics: normalizeGitMetricsConfig(metrics),
|
|
513
414
|
};
|
|
514
415
|
}
|
|
515
416
|
|
|
@@ -536,14 +437,6 @@ function booleanValue(record: Record<string, unknown>, key: keyof UiFeaturesConf
|
|
|
536
437
|
return typeof value === "boolean" ? value : defaultConfig.features[key];
|
|
537
438
|
}
|
|
538
439
|
|
|
539
|
-
function footerSegmentValue(
|
|
540
|
-
record: Record<string, unknown>,
|
|
541
|
-
key: keyof FooterSegmentsConfig,
|
|
542
|
-
): boolean {
|
|
543
|
-
const value = record[key];
|
|
544
|
-
return typeof value === "boolean" ? value : defaultConfig.footerSegments[key];
|
|
545
|
-
}
|
|
546
|
-
|
|
547
440
|
function definedColors(
|
|
548
441
|
colors: Partial<Record<keyof PolishedTuiConfig["colors"], string | undefined>>,
|
|
549
442
|
): Partial<PolishedTuiConfig["colors"]> {
|
|
@@ -567,9 +460,8 @@ function normalizeIconOverrides(record: Record<string, unknown>): Partial<IconGl
|
|
|
567
460
|
* A named colour palette, so a whole scheme can be restated in one place
|
|
568
461
|
* instead of repeating hex values across every segment.
|
|
569
462
|
*
|
|
570
|
-
* Expansion is scoped to colour records only —
|
|
571
|
-
*
|
|
572
|
-
* never be touched by it.
|
|
463
|
+
* Expansion is scoped to colour records only — keys and other strings are
|
|
464
|
+
* never touched by it.
|
|
573
465
|
*/
|
|
574
466
|
export type Palette = Record<string, string>;
|
|
575
467
|
|
|
@@ -630,40 +522,30 @@ function normalizeColors(record: Record<string, unknown>): Partial<PolishedTuiCo
|
|
|
630
522
|
return definedColors({
|
|
631
523
|
model: colorValue(record, "model"),
|
|
632
524
|
thinking: colorValue(record, "thinking"),
|
|
525
|
+
queue: colorValue(record, "queue"),
|
|
526
|
+
subagents: colorValue(record, "subagents"),
|
|
633
527
|
cwd: colorValue(record, "cwd") ?? colorValue(record, "cwdText"),
|
|
634
528
|
sessionName: colorValue(record, "sessionName"),
|
|
635
529
|
gitBranch: colorValue(record, "gitBranch") ?? colorValue(record, "git"),
|
|
636
|
-
gitStatus: colorValue(record, "gitStatus"),
|
|
637
530
|
contextNormal: colorValue(record, "contextNormal"),
|
|
638
531
|
contextWarning: colorValue(record, "contextWarning"),
|
|
639
532
|
contextError: colorValue(record, "contextError"),
|
|
640
|
-
tokens: colorValue(record, "tokens"),
|
|
641
|
-
cacheHit: colorValue(record, "cacheHit"),
|
|
642
533
|
cost: colorValue(record, "cost"),
|
|
643
|
-
|
|
534
|
+
tps: colorValue(record, "tps"),
|
|
644
535
|
runtimePrefix: colorValue(record, "runtimePrefix"),
|
|
645
536
|
extensionStatus: colorValue(record, "extensionStatus"),
|
|
646
537
|
sessionDuration: colorValue(record, "sessionDuration"),
|
|
647
538
|
packageVersion: colorValue(record, "packageVersion"),
|
|
648
539
|
gitCommit: colorValue(record, "gitCommit"),
|
|
649
540
|
gitMetricsAdded: colorValue(record, "gitMetricsAdded"),
|
|
650
|
-
gitMetricsDeleted: colorValue(record, "gitMetricsDeleted"),
|
|
651
541
|
username: colorValue(record, "username"),
|
|
652
542
|
time: colorValue(record, "time"),
|
|
653
543
|
os: colorValue(record, "os"),
|
|
654
|
-
userMessageBorder: colorValue(record, "userMessageBorder"),
|
|
655
|
-
userMessageText: colorValue(record, "userMessageText"),
|
|
656
544
|
editorAccent: colorValue(record, "editorAccent"),
|
|
657
545
|
editorPrompt: colorValue(record, "editorPrompt"),
|
|
658
546
|
editorBorder: colorValue(record, "editorBorder"),
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
editorThinking: colorValue(record, "editorThinking"),
|
|
662
|
-
editorThinkingMinimal: colorValue(record, "editorThinkingMinimal"),
|
|
663
|
-
editorThinkingLow: colorValue(record, "editorThinkingLow"),
|
|
664
|
-
editorThinkingMedium: colorValue(record, "editorThinkingMedium"),
|
|
665
|
-
editorThinkingHigh: colorValue(record, "editorThinkingHigh"),
|
|
666
|
-
editorThinkingXhigh: colorValue(record, "editorThinkingXhigh"),
|
|
547
|
+
userMessageBorder: colorValue(record, "userMessageBorder"),
|
|
548
|
+
userMessageText: colorValue(record, "userMessageText"),
|
|
667
549
|
});
|
|
668
550
|
}
|
|
669
551
|
|
|
@@ -697,47 +579,34 @@ function parseBoxPadding(value: unknown, fallback: number): number {
|
|
|
697
579
|
return value === 0 || value === 1 ? value : fallback;
|
|
698
580
|
}
|
|
699
581
|
|
|
582
|
+
const MAX_EDITOR_MIN_ROWS = 10;
|
|
583
|
+
|
|
584
|
+
function parseEditorMinRows(value: unknown): number {
|
|
585
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
|
|
586
|
+
return defaultConfig.editorMinRows;
|
|
587
|
+
}
|
|
588
|
+
return Math.min(MAX_EDITOR_MIN_ROWS, value);
|
|
589
|
+
}
|
|
590
|
+
|
|
700
591
|
function normalizeSegmentOptions(value: unknown): SegmentOptionsConfig {
|
|
701
592
|
const record = isRecord(value) ? value : {};
|
|
702
593
|
const context = isRecord(record.context) ? record.context : {};
|
|
703
594
|
const tokens = isRecord(record.tokens) ? record.tokens : {};
|
|
595
|
+
const subagents = isRecord(record.subagents) ? record.subagents : {};
|
|
704
596
|
const cache = tokens.cache;
|
|
705
597
|
return {
|
|
706
598
|
context: { format: context.format === "percent" ? "percent" : "full" },
|
|
707
599
|
tokens: {
|
|
708
600
|
cache: cache === "tokens" || cache === "off" || cache === "percent" ? cache : "percent",
|
|
709
601
|
},
|
|
710
|
-
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
function normalizeFooterSegments(record: Record<string, unknown>): FooterSegmentsConfig {
|
|
714
|
-
return {
|
|
715
|
-
model: footerSegmentValue(record, "model"),
|
|
716
|
-
thinking: footerSegmentValue(record, "thinking"),
|
|
717
|
-
cwd: footerSegmentValue(record, "cwd"),
|
|
718
|
-
sessionName: footerSegmentValue(record, "sessionName"),
|
|
719
|
-
gitBranch: footerSegmentValue(record, "gitBranch"),
|
|
720
|
-
gitStatus: footerSegmentValue(record, "gitStatus"),
|
|
721
|
-
gitCounts: footerSegmentValue(record, "gitCounts"),
|
|
722
|
-
runtime: footerSegmentValue(record, "runtime"),
|
|
723
|
-
context: footerSegmentValue(record, "context"),
|
|
724
|
-
tokens: footerSegmentValue(record, "tokens"),
|
|
725
|
-
cacheHit: footerSegmentValue(record, "cacheHit"),
|
|
726
|
-
cost: footerSegmentValue(record, "cost"),
|
|
727
|
-
sessionDuration: footerSegmentValue(record, "sessionDuration"),
|
|
728
|
-
username: footerSegmentValue(record, "username"),
|
|
729
|
-
time: footerSegmentValue(record, "time"),
|
|
730
|
-
os: footerSegmentValue(record, "os"),
|
|
731
|
-
packageVersion: footerSegmentValue(record, "packageVersion"),
|
|
732
|
-
gitCommit: footerSegmentValue(record, "gitCommit"),
|
|
733
|
-
gitMetrics: footerSegmentValue(record, "gitMetrics"),
|
|
602
|
+
subagents: { format: subagents.format === "count" ? "count" : "agents" },
|
|
734
603
|
};
|
|
735
604
|
}
|
|
736
605
|
|
|
737
606
|
/** Clamp hashLength to Git's valid abbreviation range [4, 40]. */
|
|
738
607
|
function normalizeGitHashLength(value: unknown): number {
|
|
739
608
|
const parsed = typeof value === "number" ? value : Number(value);
|
|
740
|
-
if (!Number.isFinite(parsed)) return defaultConfig.
|
|
609
|
+
if (!Number.isFinite(parsed)) return defaultConfig.git.commit.hashLength;
|
|
741
610
|
const rounded = Math.round(parsed);
|
|
742
611
|
return Math.min(40, Math.max(4, rounded));
|
|
743
612
|
}
|
|
@@ -748,8 +617,9 @@ function normalizeGitCommitConfig(record: Record<string, unknown>): GitCommitCon
|
|
|
748
617
|
onlyDetached:
|
|
749
618
|
typeof record.onlyDetached === "boolean"
|
|
750
619
|
? record.onlyDetached
|
|
751
|
-
: defaultConfig.
|
|
752
|
-
showTag:
|
|
620
|
+
: defaultConfig.git.commit.onlyDetached,
|
|
621
|
+
showTag:
|
|
622
|
+
typeof record.showTag === "boolean" ? record.showTag : defaultConfig.git.commit.showTag,
|
|
753
623
|
};
|
|
754
624
|
}
|
|
755
625
|
|
|
@@ -758,22 +628,26 @@ function normalizeGitMetricsConfig(record: Record<string, unknown>): GitMetricsC
|
|
|
758
628
|
onlyNonzero:
|
|
759
629
|
typeof record.onlyNonzero === "boolean"
|
|
760
630
|
? record.onlyNonzero
|
|
761
|
-
: defaultConfig.
|
|
631
|
+
: defaultConfig.git.metrics.onlyNonzero,
|
|
762
632
|
ignoreSubmodules:
|
|
763
633
|
typeof record.ignoreSubmodules === "boolean"
|
|
764
634
|
? record.ignoreSubmodules
|
|
765
|
-
: defaultConfig.
|
|
635
|
+
: defaultConfig.git.metrics.ignoreSubmodules,
|
|
766
636
|
};
|
|
767
637
|
}
|
|
768
638
|
|
|
769
639
|
export function isExtensionStatusPlacement(value: unknown): value is ExtensionStatusPlacement {
|
|
770
|
-
return
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
640
|
+
return value === "off" || value === "left" || value === "middle" || value === "right";
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* A stored placement, read leniently: `editor` named the editor's metadata row,
|
|
645
|
+
* which 0.9 removed, so those statuses move to the right of the footer rather
|
|
646
|
+
* than disappearing.
|
|
647
|
+
*/
|
|
648
|
+
function readExtensionStatusPlacement(value: unknown): ExtensionStatusPlacement | undefined {
|
|
649
|
+
if (value === "editor") return "right";
|
|
650
|
+
return isExtensionStatusPlacement(value) ? value : undefined;
|
|
777
651
|
}
|
|
778
652
|
|
|
779
653
|
export function isExtensionStatusColorMode(value: unknown): value is ExtensionStatusColorMode {
|
|
@@ -784,15 +658,15 @@ function normalizeExtensionStatuses(
|
|
|
784
658
|
record: Record<string, unknown>,
|
|
785
659
|
palette: Palette = {},
|
|
786
660
|
): ExtensionStatusesConfig {
|
|
787
|
-
const defaultPlacement =
|
|
788
|
-
|
|
789
|
-
|
|
661
|
+
const defaultPlacement =
|
|
662
|
+
readExtensionStatusPlacement(record.defaultPlacement) ??
|
|
663
|
+
defaultConfig.extensionStatuses.defaultPlacement;
|
|
790
664
|
const placements = isRecord(record.placements)
|
|
791
665
|
? Object.fromEntries(
|
|
792
|
-
Object.entries(record.placements).
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
),
|
|
666
|
+
Object.entries(record.placements).flatMap(([key, value]) => {
|
|
667
|
+
const placement = readExtensionStatusPlacement(value);
|
|
668
|
+
return placement ? [[key, placement]] : [];
|
|
669
|
+
}),
|
|
796
670
|
)
|
|
797
671
|
: {};
|
|
798
672
|
const colorModes = isRecord(record.colorModes)
|
|
@@ -823,12 +697,22 @@ function normalizeExtensionStatuses(
|
|
|
823
697
|
)
|
|
824
698
|
: {};
|
|
825
699
|
|
|
700
|
+
const formats = isRecord(record.formats)
|
|
701
|
+
? Object.fromEntries(
|
|
702
|
+
Object.entries(record.formats).filter(
|
|
703
|
+
(entry): entry is [string, ExtensionStatusFormat] =>
|
|
704
|
+
entry[1] === "full" || entry[1] === "value",
|
|
705
|
+
),
|
|
706
|
+
)
|
|
707
|
+
: {};
|
|
708
|
+
|
|
826
709
|
return {
|
|
827
710
|
defaultPlacement,
|
|
828
711
|
placements,
|
|
829
712
|
colorModes,
|
|
830
713
|
colors,
|
|
831
714
|
icons,
|
|
715
|
+
formats,
|
|
832
716
|
};
|
|
833
717
|
}
|
|
834
718
|
|
|
@@ -864,27 +748,6 @@ function isUiFeatureKey(value: string): value is keyof UiFeaturesConfig {
|
|
|
864
748
|
return value === "editor" || value === "statusLine" || value === "copyFriendly";
|
|
865
749
|
}
|
|
866
750
|
|
|
867
|
-
function isFooterSegmentKey(value: string): value is keyof FooterSegmentsConfig {
|
|
868
|
-
return (
|
|
869
|
-
value === "cwd" ||
|
|
870
|
-
value === "sessionName" ||
|
|
871
|
-
value === "gitBranch" ||
|
|
872
|
-
value === "gitStatus" ||
|
|
873
|
-
value === "gitCounts" ||
|
|
874
|
-
value === "runtime" ||
|
|
875
|
-
value === "context" ||
|
|
876
|
-
value === "tokens" ||
|
|
877
|
-
value === "cost" ||
|
|
878
|
-
value === "sessionDuration" ||
|
|
879
|
-
value === "username" ||
|
|
880
|
-
value === "time" ||
|
|
881
|
-
value === "os" ||
|
|
882
|
-
value === "packageVersion" ||
|
|
883
|
-
value === "gitCommit" ||
|
|
884
|
-
value === "gitMetrics"
|
|
885
|
-
);
|
|
886
|
-
}
|
|
887
|
-
|
|
888
751
|
function validColorSourceEntries(record: Record<string, unknown>): Partial<ColorSourcesConfig> {
|
|
889
752
|
return Object.fromEntries(
|
|
890
753
|
Object.entries(record).filter((entry): entry is [keyof ColorSourcesConfig, ColorSource] => {
|
|
@@ -903,15 +766,6 @@ function validUiFeatureEntries(record: Record<string, unknown>): Partial<UiFeatu
|
|
|
903
766
|
) as Partial<UiFeaturesConfig>;
|
|
904
767
|
}
|
|
905
768
|
|
|
906
|
-
function validFooterSegmentEntries(record: Record<string, unknown>): Partial<FooterSegmentsConfig> {
|
|
907
|
-
return Object.fromEntries(
|
|
908
|
-
Object.entries(record).filter((entry): entry is [keyof FooterSegmentsConfig, boolean] => {
|
|
909
|
-
const [key, value] = entry;
|
|
910
|
-
return isFooterSegmentKey(key) && typeof value === "boolean";
|
|
911
|
-
}),
|
|
912
|
-
) as Partial<FooterSegmentsConfig>;
|
|
913
|
-
}
|
|
914
|
-
|
|
915
769
|
type ConfigFileState =
|
|
916
770
|
| { kind: "missing"; record: ConfigRecord; writePath: string }
|
|
917
771
|
| { kind: "valid"; record: ConfigRecord; writePath: string; mode: number }
|
|
@@ -1016,33 +870,16 @@ export function mergeConfig(parsed: unknown): PolishedTuiConfig {
|
|
|
1016
870
|
const features = isRecord(config.features)
|
|
1017
871
|
? normalizeUiFeatures(config.features as Record<string, unknown>)
|
|
1018
872
|
: defaultConfig.features;
|
|
1019
|
-
const footerSegments = isRecord(config.footerSegments)
|
|
1020
|
-
? normalizeFooterSegments(config.footerSegments as Record<string, unknown>)
|
|
1021
|
-
: defaultConfig.footerSegments;
|
|
1022
873
|
const extensionStatuses = isRecord(config.extensionStatuses)
|
|
1023
874
|
? normalizeExtensionStatuses(config.extensionStatuses as Record<string, unknown>, palette)
|
|
1024
875
|
: defaultConfig.extensionStatuses;
|
|
1025
|
-
const
|
|
1026
|
-
? normalizeGitCommitConfig(config.gitCommit as Record<string, unknown>)
|
|
1027
|
-
: defaultConfig.gitCommit;
|
|
1028
|
-
const gitMetrics = isRecord(config.gitMetrics)
|
|
1029
|
-
? normalizeGitMetricsConfig(config.gitMetrics as Record<string, unknown>)
|
|
1030
|
-
: defaultConfig.gitMetrics;
|
|
1031
|
-
const gitBranch = parseGitBranchConfig(config.gitBranch);
|
|
876
|
+
const git = parseGitConfig(config.git);
|
|
1032
877
|
const mouse = isRecord(config.mouse)
|
|
1033
878
|
? normalizeMouseConfig(config.mouse as Record<string, unknown>)
|
|
1034
879
|
: defaultConfig.mouse;
|
|
1035
|
-
const editorMetadataFormat = stringValue(config, "editorMetadataFormat");
|
|
1036
880
|
return {
|
|
1037
881
|
projectRefreshIntervalMs: parseProjectRefreshIntervalMs(config.projectRefreshIntervalMs),
|
|
1038
|
-
footerStyle: parseFooterStyle(config.footerStyle),
|
|
1039
882
|
pill: normalizePillConfig(config.pill),
|
|
1040
|
-
footerFormat: stringValue(config, "footerFormat") ?? "",
|
|
1041
|
-
// An explicit "" means "show nothing", which is how the model/thinking
|
|
1042
|
-
// segments get moved down to the footer without leaving an empty row.
|
|
1043
|
-
// Only a missing or non-string value falls back to the default.
|
|
1044
|
-
editorMetadataFormat: editorMetadataFormat ?? DEFAULT_EDITOR_METADATA_FORMAT,
|
|
1045
|
-
separator: parseSeparatorStyle(config.separator),
|
|
1046
883
|
contextStyle: parseContextStyle(config.contextStyle),
|
|
1047
884
|
segmentOptions: normalizeSegmentOptions(config.segmentOptions),
|
|
1048
885
|
editorModelLabel: parseEditorModelLabel(config.editorModelLabel),
|
|
@@ -1050,14 +887,14 @@ export function mergeConfig(parsed: unknown): PolishedTuiConfig {
|
|
|
1050
887
|
editorClickCursor: config.editorClickCursor !== false,
|
|
1051
888
|
pasteCollapseLines: parsePasteCollapseLines(config.pasteCollapseLines),
|
|
1052
889
|
editorPaddingY: parseBoxPadding(config.editorPaddingY, defaultConfig.editorPaddingY),
|
|
890
|
+
editorMinRows: parseEditorMinRows(config.editorMinRows),
|
|
1053
891
|
userMessagePaddingY: parseBoxPadding(
|
|
1054
892
|
config.userMessagePaddingY,
|
|
1055
893
|
defaultConfig.userMessagePaddingY,
|
|
1056
894
|
),
|
|
1057
895
|
contextThresholds: parseContextThresholds(config.contextThresholds),
|
|
1058
896
|
pathDisplay: parsePathDisplay(config.pathDisplay),
|
|
1059
|
-
|
|
1060
|
-
gitHostIcon: config.gitHostIcon === true,
|
|
897
|
+
git,
|
|
1061
898
|
icons: resolveConfiguredIcons(iconMode, iconOverrides),
|
|
1062
899
|
colors: {
|
|
1063
900
|
...defaultConfig.colors,
|
|
@@ -1065,15 +902,13 @@ export function mergeConfig(parsed: unknown): PolishedTuiConfig {
|
|
|
1065
902
|
},
|
|
1066
903
|
colorSources: { ...colorSources },
|
|
1067
904
|
features: { ...features },
|
|
1068
|
-
footerSegments: { ...footerSegments },
|
|
1069
|
-
gitCommit,
|
|
1070
|
-
gitMetrics,
|
|
1071
905
|
extensionStatuses: {
|
|
1072
906
|
defaultPlacement: extensionStatuses.defaultPlacement,
|
|
1073
907
|
placements: { ...extensionStatuses.placements },
|
|
1074
908
|
colorModes: { ...extensionStatuses.colorModes },
|
|
1075
909
|
colors: { ...extensionStatuses.colors },
|
|
1076
910
|
icons: { ...extensionStatuses.icons },
|
|
911
|
+
formats: { ...extensionStatuses.formats },
|
|
1077
912
|
},
|
|
1078
913
|
mouse,
|
|
1079
914
|
};
|
|
@@ -1101,6 +936,14 @@ export function getExtensionStatusIcon(config: PolishedTuiConfig, key: string):
|
|
|
1101
936
|
return config.extensionStatuses.icons[key] ?? "";
|
|
1102
937
|
}
|
|
1103
938
|
|
|
939
|
+
/** Text shape for one status. Unset means the text the extension published. */
|
|
940
|
+
export function getExtensionStatusFormat(
|
|
941
|
+
config: PolishedTuiConfig,
|
|
942
|
+
key: string,
|
|
943
|
+
): ExtensionStatusFormat {
|
|
944
|
+
return config.extensionStatuses.formats[key] ?? "full";
|
|
945
|
+
}
|
|
946
|
+
|
|
1104
947
|
export function getExtensionStatusColorMode(
|
|
1105
948
|
config: PolishedTuiConfig,
|
|
1106
949
|
key: string,
|
|
@@ -1108,16 +951,16 @@ export function getExtensionStatusColorMode(
|
|
|
1108
951
|
return config.extensionStatuses.colorModes[key] ?? DEFAULT_EXTENSION_STATUS_COLOR_MODE;
|
|
1109
952
|
}
|
|
1110
953
|
|
|
1111
|
-
export function loadConfig(): PolishedTuiConfig {
|
|
954
|
+
export function loadConfig(path = configPath): PolishedTuiConfig {
|
|
1112
955
|
try {
|
|
1113
|
-
if (!existsSync(
|
|
1114
|
-
const parsed = JSON.parse(readFileSync(
|
|
956
|
+
if (!existsSync(path)) return mergeConfig({});
|
|
957
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
1115
958
|
if (!isRecord(parsed)) return mergeConfig(parsed);
|
|
1116
959
|
|
|
1117
960
|
const { config, migrated } = migrateFixedEditorKeys(parsed);
|
|
1118
961
|
if (migrated) {
|
|
1119
962
|
try {
|
|
1120
|
-
writeConfigAtomically(
|
|
963
|
+
writeConfigAtomically(path, config);
|
|
1121
964
|
} catch {
|
|
1122
965
|
// Best effort: still use the migrated config for this run even if
|
|
1123
966
|
// the write-back fails (e.g. read-only filesystem).
|
|
@@ -1127,7 +970,13 @@ export function loadConfig(): PolishedTuiConfig {
|
|
|
1127
970
|
);
|
|
1128
971
|
}
|
|
1129
972
|
return mergeConfig(config);
|
|
1130
|
-
} catch {
|
|
973
|
+
} catch (error) {
|
|
974
|
+
// Falling back to defaults is right — a broken config must not block a
|
|
975
|
+
// session — but silently forgetting every setting is not. `mutateConfig`
|
|
976
|
+
// refuses loudly on the same file, so say which file was given up on.
|
|
977
|
+
console.warn(
|
|
978
|
+
`[starline] Could not read ${path}; using defaults for this session. ${error instanceof Error ? error.message : String(error)}`,
|
|
979
|
+
);
|
|
1131
980
|
return mergeConfig({});
|
|
1132
981
|
}
|
|
1133
982
|
}
|
|
@@ -1162,27 +1011,6 @@ export function saveUiFeaturesPatch(
|
|
|
1162
1011
|
});
|
|
1163
1012
|
}
|
|
1164
1013
|
|
|
1165
|
-
export function saveFooterSegmentsPatch(
|
|
1166
|
-
patch: Partial<FooterSegmentsConfig>,
|
|
1167
|
-
path = configPath,
|
|
1168
|
-
): PolishedTuiConfig {
|
|
1169
|
-
return mutateConfig(path, (record) => {
|
|
1170
|
-
const existing = isRecord(record.footerSegments)
|
|
1171
|
-
? { ...(record.footerSegments as Record<string, unknown>) }
|
|
1172
|
-
: {};
|
|
1173
|
-
record.footerSegments = {
|
|
1174
|
-
...existing,
|
|
1175
|
-
...validFooterSegmentEntries(patch),
|
|
1176
|
-
};
|
|
1177
|
-
});
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
export function saveFooterFormatPatch(value: string, path = configPath): PolishedTuiConfig {
|
|
1181
|
-
return mutateConfig(path, (record) => {
|
|
1182
|
-
record.footerFormat = typeof value === "string" ? value : "";
|
|
1183
|
-
});
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
1014
|
export function saveIconsModePatch(mode: IconMode, path = configPath): PolishedTuiConfig {
|
|
1187
1015
|
return mutateConfig(path, (record) => {
|
|
1188
1016
|
const existing = isRecord(record.icons) ? { ...(record.icons as Record<string, unknown>) } : {};
|
|
@@ -1199,15 +1027,6 @@ export function saveContextStylePatch(style: ContextStyle, path = configPath): P
|
|
|
1199
1027
|
});
|
|
1200
1028
|
}
|
|
1201
1029
|
|
|
1202
|
-
export function saveSeparatorPatch(
|
|
1203
|
-
separator: SeparatorStyle,
|
|
1204
|
-
path = configPath,
|
|
1205
|
-
): PolishedTuiConfig {
|
|
1206
|
-
return mutateConfig(path, (record) => {
|
|
1207
|
-
record.separator = parseSeparatorStyle(separator);
|
|
1208
|
-
});
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
1030
|
export function saveContextThresholdsPatch(
|
|
1212
1031
|
thresholds: Partial<ContextThresholds>,
|
|
1213
1032
|
path = configPath,
|
|
@@ -1237,17 +1056,28 @@ export function savePathDisplayPatch(
|
|
|
1237
1056
|
});
|
|
1238
1057
|
}
|
|
1239
1058
|
|
|
1059
|
+
export function savePillSegmentsPatch(
|
|
1060
|
+
segments: readonly string[],
|
|
1061
|
+
path = configPath,
|
|
1062
|
+
): PolishedTuiConfig {
|
|
1063
|
+
return mutateConfig(path, (record) => {
|
|
1064
|
+
const existing = isRecord(record.pill) ? { ...(record.pill as Record<string, unknown>) } : {};
|
|
1065
|
+
record.pill = { ...existing, segments: [...segments] };
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1240
1069
|
export function saveGitBranchPatch(
|
|
1241
1070
|
patch: Partial<GitBranchConfig>,
|
|
1242
1071
|
path = configPath,
|
|
1243
1072
|
): PolishedTuiConfig {
|
|
1244
1073
|
return mutateConfig(path, (record) => {
|
|
1245
|
-
const
|
|
1246
|
-
|
|
1074
|
+
const existingGit = isRecord(record.git) ? { ...(record.git as Record<string, unknown>) } : {};
|
|
1075
|
+
const existingBranch = isRecord(existingGit.branch)
|
|
1076
|
+
? { ...(existingGit.branch as Record<string, unknown>) }
|
|
1247
1077
|
: {};
|
|
1248
1078
|
if (patch.maxLength !== undefined)
|
|
1249
|
-
|
|
1250
|
-
record.
|
|
1079
|
+
existingBranch.maxLength = normalizeGitBranchMaxLength(patch.maxLength);
|
|
1080
|
+
record.git = { ...existingGit, branch: existingBranch };
|
|
1251
1081
|
});
|
|
1252
1082
|
}
|
|
1253
1083
|
|