pi-one-ui 0.1.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/LICENSE +21 -0
- package/README.md +106 -0
- package/extensions/app/config/renderer.ts +452 -0
- package/extensions/app/config/shell.ts +2484 -0
- package/extensions/app/ownership.ts +18 -0
- package/extensions/app/panel.ts +533 -0
- package/extensions/app/presets.ts +36 -0
- package/extensions/features/agent-summary/core.ts +194 -0
- package/extensions/features/agent-summary/index.ts +37 -0
- package/extensions/features/compact-thinking.ts +1262 -0
- package/extensions/features/context.ts +857 -0
- package/extensions/features/reference/index.ts +643 -0
- package/extensions/features/reference/session.ts +278 -0
- package/extensions/features/reference/subagent.ts +218 -0
- package/extensions/features/shell/aliases.ts +28 -0
- package/extensions/features/shell/flush-docked-bash.ts +39 -0
- package/extensions/features/shell/startup-header.ts +357 -0
- package/extensions/features/shell/working-message.ts +226 -0
- package/extensions/index.ts +52 -0
- package/extensions/shell/accent-rail-editor.ts +112 -0
- package/extensions/shell/accent-rail-layout-patch.ts +530 -0
- package/extensions/shell/completion-menu.ts +186 -0
- package/extensions/shell/editor-metadata-format.ts +287 -0
- package/extensions/shell/editor-transfer.ts +73 -0
- package/extensions/shell/extension-status.ts +102 -0
- package/extensions/shell/footer-format.ts +388 -0
- package/extensions/shell/footer-layout.ts +144 -0
- package/extensions/shell/footer.ts +1035 -0
- package/extensions/shell/format.ts +577 -0
- package/extensions/shell/git.ts +434 -0
- package/extensions/shell/icons.ts +261 -0
- package/extensions/shell/index.ts +1659 -0
- package/extensions/shell/interaction-summary.ts +1194 -0
- package/extensions/shell/live-context.ts +82 -0
- package/extensions/shell/minimalist-editor.ts +548 -0
- package/extensions/shell/package-version.ts +675 -0
- package/extensions/shell/project-refresh.ts +130 -0
- package/extensions/shell/project-state.ts +60 -0
- package/extensions/shell/prototype-patch-registry.ts +183 -0
- package/extensions/shell/runtime.ts +915 -0
- package/extensions/shell/selector-border.ts +109 -0
- package/extensions/shell/session-lifecycle.ts +60 -0
- package/extensions/shell/settings-command.ts +2144 -0
- package/extensions/shell/settings-previews.ts +137 -0
- package/extensions/shell/state.ts +83 -0
- package/extensions/shell/style.ts +480 -0
- package/extensions/shell/telemetry.ts +90 -0
- package/extensions/shell/ui.ts +1326 -0
- package/extensions/shell/user-message-osc.ts +279 -0
- package/extensions/shell/user-message-styles.ts +266 -0
- package/extensions/shell/user-message.ts +226 -0
- package/extensions/shell/working-line-messages.ts +24 -0
- package/extensions/shell/working-line-spinners.ts +54 -0
- package/extensions/shell/working-line.ts +1603 -0
- package/extensions/tools/ansi-color.ts +44 -0
- package/extensions/tools/ansi-text.ts +52 -0
- package/extensions/tools/component-tree.ts +37 -0
- package/extensions/tools/format.ts +28 -0
- package/extensions/tools/fullscreen-detect.ts +11 -0
- package/extensions/tools/patch-keys.ts +158 -0
- package/extensions/tools/sgr-mouse.ts +59 -0
- package/extensions/tools/tool-loading-icon.ts +20 -0
- package/extensions/tools/tool-result-sanitize.ts +27 -0
- package/extensions/transcript/index.ts +65 -0
- package/extensions/transcript/renderer/compact-mode.ts +1443 -0
- package/extensions/transcript/renderer/default-mode.ts +735 -0
- package/extensions/transcript/renderer/index.ts +286 -0
- package/extensions/transcript/renderer/markdown-enhance.ts +325 -0
- package/extensions/transcript/renderer/mouse/hover.ts +175 -0
- package/extensions/transcript/renderer/mouse/interaction.ts +1016 -0
- package/extensions/transcript/renderer/mouse/layout.ts +207 -0
- package/extensions/transcript/renderer/mouse/packets.ts +96 -0
- package/extensions/transcript/renderer/mouse/scroll.ts +211 -0
- package/extensions/transcript/renderer/tool/diff/ATTRIBUTION.md +28 -0
- package/extensions/transcript/renderer/tool/diff/ansi-utils.ts +128 -0
- package/extensions/transcript/renderer/tool/diff/diff-component.ts +112 -0
- package/extensions/transcript/renderer/tool/diff/diff-edit-render.ts +255 -0
- package/extensions/transcript/renderer/tool/diff/diff-header.ts +184 -0
- package/extensions/transcript/renderer/tool/diff/diff-highlight.ts +131 -0
- package/extensions/transcript/renderer/tool/diff/diff-inline.ts +350 -0
- package/extensions/transcript/renderer/tool/diff/diff-layout.ts +870 -0
- package/extensions/transcript/renderer/tool/diff/diff-limits.ts +189 -0
- package/extensions/transcript/renderer/tool/diff/diff-palette.ts +436 -0
- package/extensions/transcript/renderer/tool/diff/diff-parse.ts +536 -0
- package/extensions/transcript/renderer/tool/diff/diff-presentation.ts +79 -0
- package/extensions/transcript/renderer/tool/diff/diff-renderer.ts +29 -0
- package/extensions/transcript/renderer/tool/diff/diff-text.ts +57 -0
- package/extensions/transcript/renderer/tool/diff/diff-write-render.ts +629 -0
- package/extensions/transcript/renderer/tool/diff/index.ts +147 -0
- package/extensions/transcript/renderer/tool/diff/line-width-safety.ts +100 -0
- package/extensions/transcript/renderer/tool/diff/shiki-highlight.ts +104 -0
- package/extensions/transcript/renderer/tool/diff/write-execution.ts +124 -0
- package/extensions/transcript/renderer/tool/grouping.ts +619 -0
- package/extensions/transcript/renderer/tool/message-display.ts +183 -0
- package/extensions/transcript/renderer/tool/names.ts +195 -0
- package/extensions/transcript/renderer/tool/result.ts +842 -0
- package/extensions/transcript/renderer/tool/show-more-hint.ts +42 -0
- package/extensions/transcript/renderer/transcript-refresh.ts +79 -0
- package/package.json +58 -0
- package/themes/cc-dark.json +91 -0
- package/themes/cc-light.json +91 -0
|
@@ -0,0 +1,2484 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
closeSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
fchmodSync,
|
|
6
|
+
fsyncSync,
|
|
7
|
+
lstatSync,
|
|
8
|
+
openSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
realpathSync,
|
|
11
|
+
renameSync,
|
|
12
|
+
statSync,
|
|
13
|
+
unlinkSync,
|
|
14
|
+
writeFileSync,
|
|
15
|
+
} from "node:fs";
|
|
16
|
+
import { basename, dirname, join } from "node:path";
|
|
17
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
18
|
+
import {
|
|
19
|
+
ICON_GLYPH_KEYS,
|
|
20
|
+
type IconGlyphs,
|
|
21
|
+
type IconMode,
|
|
22
|
+
NERD_DEFAULT_ICONS,
|
|
23
|
+
normalizeIconMode,
|
|
24
|
+
type ResolvedIcons,
|
|
25
|
+
resolveConfiguredIcons,
|
|
26
|
+
} from "../../shell/icons.ts";
|
|
27
|
+
import { isSupportedColorSpec } from "../../shell/style.ts";
|
|
28
|
+
import { normalizeWorkingLineMessages } from "../../shell/working-line.ts";
|
|
29
|
+
import { PI_WORKING_LINE_MESSAGES } from "../../shell/working-line-messages.ts";
|
|
30
|
+
|
|
31
|
+
export type ColorSpec = string;
|
|
32
|
+
export type ColorSource = "theme" | "terminal";
|
|
33
|
+
export type { IconMode } from "../../shell/icons.ts";
|
|
34
|
+
|
|
35
|
+
export type ContextStyle = "text" | "gauge" | "text+gauge";
|
|
36
|
+
export type SeparatorStyle = "pipe" | "dot" | "chevron" | "none";
|
|
37
|
+
export type ModelLabelSource = "id" | "name";
|
|
38
|
+
export type EditorStyle =
|
|
39
|
+
| "opencode"
|
|
40
|
+
| "opencode-copy-friendly"
|
|
41
|
+
| "accent-rail"
|
|
42
|
+
| "minimalist";
|
|
43
|
+
export type UserMessageStyle =
|
|
44
|
+
| "framed"
|
|
45
|
+
| "framed-copy-friendly"
|
|
46
|
+
| "compact"
|
|
47
|
+
| "labeled";
|
|
48
|
+
export type SelectorBorderStyle = "zentui";
|
|
49
|
+
export type FooterStyle = "native" | "starship" | "hidden";
|
|
50
|
+
export type WorkingLineSpinner =
|
|
51
|
+
| "braille"
|
|
52
|
+
| "star-bloom"
|
|
53
|
+
| "pinwheel"
|
|
54
|
+
| "claude-inspired"
|
|
55
|
+
| "pulse";
|
|
56
|
+
export type WorkingLineTextAnimation = "classic" | "kitt" | "disabled";
|
|
57
|
+
export type ComponentStyleOwner =
|
|
58
|
+
| "editor"
|
|
59
|
+
| "userMessages"
|
|
60
|
+
| "selectorBorders"
|
|
61
|
+
| "footer";
|
|
62
|
+
export type MinimalistPathDisplayMode = "compact" | "project" | "full";
|
|
63
|
+
export type MinimalistContextFormat = "percent" | "percent-total";
|
|
64
|
+
export type EditorBorderColorMode = "static" | "adaptive";
|
|
65
|
+
export type CompletionMenuStyle = "native" | "palette";
|
|
66
|
+
export type CompactFooterMaxLines = 1 | 2 | 3 | "unlimited";
|
|
67
|
+
|
|
68
|
+
export const DEFAULT_COMPACT_FOOTER_FORMAT =
|
|
69
|
+
"$cwd$wrap(in $session_name)$wrap(on $git_branch) $git_status$wrap$context$wrap_sep$tokens";
|
|
70
|
+
|
|
71
|
+
export type ContextThresholds = {
|
|
72
|
+
warning: number;
|
|
73
|
+
error: number;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type PathDisplayMode = "basename" | "full";
|
|
77
|
+
|
|
78
|
+
export type PathDisplayConfig = {
|
|
79
|
+
mode: PathDisplayMode;
|
|
80
|
+
/** Trailing directories to show in full mode. 0 = unlimited; clamped to 0..5. */
|
|
81
|
+
depth: number;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type GitBranchMaxLength = "full" | number;
|
|
85
|
+
|
|
86
|
+
export type GitBranchConfig = {
|
|
87
|
+
maxLength: GitBranchMaxLength;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type ColorSourcesConfig = {
|
|
91
|
+
starship: ColorSource;
|
|
92
|
+
editor: ColorSource;
|
|
93
|
+
userMessages: ColorSource;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type UiFeaturesConfig = {
|
|
97
|
+
editor: boolean;
|
|
98
|
+
statusLine: boolean;
|
|
99
|
+
viewportIndicators: boolean;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export type FooterSegmentsConfig = {
|
|
103
|
+
cwd: boolean;
|
|
104
|
+
sessionName: boolean;
|
|
105
|
+
gitBranch: boolean;
|
|
106
|
+
gitStatus: boolean;
|
|
107
|
+
gitCounts: boolean;
|
|
108
|
+
gitCommit: boolean;
|
|
109
|
+
gitMetrics: boolean;
|
|
110
|
+
runtime: boolean;
|
|
111
|
+
modelInfo: boolean;
|
|
112
|
+
context: boolean;
|
|
113
|
+
tokens: boolean;
|
|
114
|
+
cost: boolean;
|
|
115
|
+
sessionDuration: boolean;
|
|
116
|
+
username: boolean;
|
|
117
|
+
time: boolean;
|
|
118
|
+
os: boolean;
|
|
119
|
+
packageVersion: boolean;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export type PolishedEditorStyleConfig = {
|
|
123
|
+
metadataFormat: string;
|
|
124
|
+
completionMenu: CompletionMenuStyle;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export type PolishedCopyFriendlyEditorStyleConfig = {
|
|
128
|
+
metadataFormat: string;
|
|
129
|
+
completionMenu: CompletionMenuStyle;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export type AccentRailEditorStyleConfig = {
|
|
133
|
+
rail: string;
|
|
134
|
+
asciiRail: string;
|
|
135
|
+
transparent: boolean;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export type MinimalistEditorStyleConfig = {
|
|
139
|
+
pathDisplay: MinimalistPathDisplayMode;
|
|
140
|
+
contextFormat: MinimalistContextFormat;
|
|
141
|
+
contextGauge: boolean;
|
|
142
|
+
showSessionName: boolean;
|
|
143
|
+
showTimer: boolean;
|
|
144
|
+
showCost: boolean;
|
|
145
|
+
showGit: boolean;
|
|
146
|
+
contextThresholds: ContextThresholds;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/** Temporary name retained for existing settings consumers. */
|
|
150
|
+
export type MinimalistConfig = MinimalistEditorStyleConfig;
|
|
151
|
+
|
|
152
|
+
export type EditorStylesConfig = {
|
|
153
|
+
minimalist: MinimalistEditorStyleConfig;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export type EditorComponentConfig = {
|
|
157
|
+
enabled: boolean;
|
|
158
|
+
style: EditorStyle;
|
|
159
|
+
colorSource: ColorSource;
|
|
160
|
+
borderColorMode: EditorBorderColorMode;
|
|
161
|
+
modelLabel: ModelLabelSource;
|
|
162
|
+
viewportIndicators: boolean;
|
|
163
|
+
styles: {
|
|
164
|
+
opencode: PolishedEditorStyleConfig;
|
|
165
|
+
"opencode-copy-friendly": PolishedCopyFriendlyEditorStyleConfig;
|
|
166
|
+
"accent-rail": AccentRailEditorStyleConfig;
|
|
167
|
+
minimalist: MinimalistEditorStyleConfig;
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export type FramedUserMessageStyleConfig = Record<string, never>;
|
|
172
|
+
export type FramedCopyFriendlyUserMessageStyleConfig = Record<string, never>;
|
|
173
|
+
export type CompactUserMessageStyleConfig = Record<string, never>;
|
|
174
|
+
export type LabeledUserMessageStyleConfig = Record<string, never>;
|
|
175
|
+
|
|
176
|
+
export type UserMessagesComponentConfig = {
|
|
177
|
+
enabled: boolean;
|
|
178
|
+
style: UserMessageStyle;
|
|
179
|
+
colorSource: ColorSource;
|
|
180
|
+
styles: {
|
|
181
|
+
framed: FramedUserMessageStyleConfig;
|
|
182
|
+
"framed-copy-friendly": FramedCopyFriendlyUserMessageStyleConfig;
|
|
183
|
+
compact: CompactUserMessageStyleConfig;
|
|
184
|
+
labeled: LabeledUserMessageStyleConfig;
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export type SelectorBordersComponentConfig = {
|
|
189
|
+
enabled: boolean;
|
|
190
|
+
style: SelectorBorderStyle;
|
|
191
|
+
colorSource: ColorSource;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export type StarshipFooterStyleConfig = {
|
|
195
|
+
format: string;
|
|
196
|
+
responsive: boolean;
|
|
197
|
+
compactFormat: string;
|
|
198
|
+
compactMaxLines: CompactFooterMaxLines;
|
|
199
|
+
separator: SeparatorStyle;
|
|
200
|
+
contextStyle: ContextStyle;
|
|
201
|
+
contextThresholds: ContextThresholds;
|
|
202
|
+
pathDisplay: PathDisplayConfig;
|
|
203
|
+
segments: FooterSegmentsConfig;
|
|
204
|
+
gitBranch: GitBranchConfig;
|
|
205
|
+
gitCommit: GitCommitConfig;
|
|
206
|
+
gitMetrics: GitMetricsConfig;
|
|
207
|
+
extensionStatuses: ExtensionStatusesConfig;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export type FooterComponentConfig = {
|
|
211
|
+
style: FooterStyle;
|
|
212
|
+
colorSource: ColorSource;
|
|
213
|
+
modelLabel: ModelLabelSource;
|
|
214
|
+
styles: {
|
|
215
|
+
starship: StarshipFooterStyleConfig;
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export type WorkingLineMessagesConfig = {
|
|
220
|
+
custom: boolean;
|
|
221
|
+
values: string[];
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export type WorkingLineSegmentsConfig = {
|
|
225
|
+
tool: boolean;
|
|
226
|
+
elapsed: boolean;
|
|
227
|
+
thought: boolean;
|
|
228
|
+
tokens: boolean;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
export const DEFAULT_WORKING_LINE_SPINNER_INTERVAL_MS = 100;
|
|
232
|
+
export const DEFAULT_WORKING_LINE_TEXT_INTERVAL_MS = 60;
|
|
233
|
+
export const MIN_WORKING_LINE_INTERVAL_MS = 30;
|
|
234
|
+
export const MAX_WORKING_LINE_INTERVAL_MS = 1000;
|
|
235
|
+
|
|
236
|
+
export function isValidWorkingLineIntervalMs(value: unknown): value is number {
|
|
237
|
+
return (
|
|
238
|
+
typeof value === "number" &&
|
|
239
|
+
Number.isSafeInteger(value) &&
|
|
240
|
+
value >= MIN_WORKING_LINE_INTERVAL_MS &&
|
|
241
|
+
value <= MAX_WORKING_LINE_INTERVAL_MS
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export type WorkingLineComponentConfig = {
|
|
246
|
+
enabled: boolean;
|
|
247
|
+
turnSummary: boolean;
|
|
248
|
+
spinner: WorkingLineSpinner;
|
|
249
|
+
spinnerIntervalMs: number;
|
|
250
|
+
animateSpinnerColor: boolean;
|
|
251
|
+
textIntervalMs: number;
|
|
252
|
+
textAnimation: WorkingLineTextAnimation;
|
|
253
|
+
colorSource: ColorSource;
|
|
254
|
+
messages: WorkingLineMessagesConfig;
|
|
255
|
+
segments: WorkingLineSegmentsConfig;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
export type WorkingLineComponentPatch = Partial<
|
|
259
|
+
Omit<WorkingLineComponentConfig, "messages" | "segments">
|
|
260
|
+
> & {
|
|
261
|
+
messages?: Partial<WorkingLineMessagesConfig>;
|
|
262
|
+
segments?: Partial<WorkingLineSegmentsConfig>;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export type ComponentsConfig = {
|
|
266
|
+
editor: EditorComponentConfig;
|
|
267
|
+
userMessages: UserMessagesComponentConfig;
|
|
268
|
+
workingLine: WorkingLineComponentConfig;
|
|
269
|
+
selectorBorders: SelectorBordersComponentConfig;
|
|
270
|
+
footer: FooterComponentConfig;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
export type ExtensionStatusPlacement = "off" | "left" | "middle" | "right";
|
|
274
|
+
export type ExtensionStatusColorMode = "zentui" | "original";
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Starship `git_commit`-style options.
|
|
278
|
+
* See https://starship.rs/config/#git-commit
|
|
279
|
+
*/
|
|
280
|
+
export type GitCommitConfig = {
|
|
281
|
+
hashLength: number;
|
|
282
|
+
onlyDetached: boolean;
|
|
283
|
+
showTag: boolean;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Starship `git_metrics`-style options.
|
|
288
|
+
* See https://starship.rs/config/#git-metrics
|
|
289
|
+
*/
|
|
290
|
+
export type GitMetricsConfig = {
|
|
291
|
+
onlyNonzero: boolean;
|
|
292
|
+
ignoreSubmodules: boolean;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const DEFAULT_EXTENSION_STATUS_PLACEMENT: ExtensionStatusPlacement = "right";
|
|
296
|
+
const DEFAULT_EXTENSION_STATUS_COLOR_MODE: ExtensionStatusColorMode = "zentui";
|
|
297
|
+
|
|
298
|
+
export type ExtensionStatusesConfig = {
|
|
299
|
+
defaultPlacement: ExtensionStatusPlacement;
|
|
300
|
+
placements: Record<string, ExtensionStatusPlacement>;
|
|
301
|
+
colorModes: Record<string, ExtensionStatusColorMode>;
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const DEFAULT_PROJECT_REFRESH_INTERVAL_MS = 30_000;
|
|
305
|
+
const MIN_PROJECT_REFRESH_INTERVAL_MS = 5_000;
|
|
306
|
+
export const DEFAULT_EDITOR_METADATA_FORMAT = "$model $provider( $thinking)";
|
|
307
|
+
|
|
308
|
+
export type ZentuiConfig = {
|
|
309
|
+
projectRefreshIntervalMs: number;
|
|
310
|
+
icons: ResolvedIcons;
|
|
311
|
+
colors: PolishedTuiColors;
|
|
312
|
+
components: ComponentsConfig;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
export type PolishedTuiColors = {
|
|
316
|
+
cwd: ColorSpec;
|
|
317
|
+
sessionName: ColorSpec;
|
|
318
|
+
gitBranch: ColorSpec;
|
|
319
|
+
gitStatus: ColorSpec;
|
|
320
|
+
contextNormal: ColorSpec;
|
|
321
|
+
contextWarning: ColorSpec;
|
|
322
|
+
contextError: ColorSpec;
|
|
323
|
+
tokens: ColorSpec;
|
|
324
|
+
cost: ColorSpec;
|
|
325
|
+
separator: ColorSpec;
|
|
326
|
+
runtimePrefix: ColorSpec;
|
|
327
|
+
extensionStatus: ColorSpec;
|
|
328
|
+
sessionDuration: ColorSpec;
|
|
329
|
+
packageVersion: ColorSpec;
|
|
330
|
+
gitCommit: ColorSpec;
|
|
331
|
+
gitMetricsAdded: ColorSpec;
|
|
332
|
+
gitMetricsDeleted: ColorSpec;
|
|
333
|
+
username: ColorSpec;
|
|
334
|
+
time: ColorSpec;
|
|
335
|
+
os: ColorSpec;
|
|
336
|
+
editorAccent?: ColorSpec;
|
|
337
|
+
editorRail?: ColorSpec;
|
|
338
|
+
editorPrompt?: ColorSpec;
|
|
339
|
+
editorBorder?: ColorSpec;
|
|
340
|
+
editorGitBranch?: ColorSpec;
|
|
341
|
+
editorModel?: ColorSpec;
|
|
342
|
+
editorProvider?: ColorSpec;
|
|
343
|
+
editorThinking?: ColorSpec;
|
|
344
|
+
editorThinkingMinimal?: ColorSpec;
|
|
345
|
+
editorThinkingLow?: ColorSpec;
|
|
346
|
+
editorThinkingMedium?: ColorSpec;
|
|
347
|
+
editorThinkingHigh?: ColorSpec;
|
|
348
|
+
editorThinkingXhigh?: ColorSpec;
|
|
349
|
+
editorThinkingMax?: ColorSpec;
|
|
350
|
+
workingLineLow?: ColorSpec;
|
|
351
|
+
workingLineMid?: ColorSpec;
|
|
352
|
+
workingLineHigh?: ColorSpec;
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Canonical configuration plus a temporary flat compatibility projection used
|
|
357
|
+
* by production consumers while they migrate to `components`.
|
|
358
|
+
*/
|
|
359
|
+
export type PolishedTuiConfig = ZentuiConfig & {
|
|
360
|
+
footerFormat: string;
|
|
361
|
+
responsiveFooter: boolean;
|
|
362
|
+
compactFooterFormat: string;
|
|
363
|
+
compactFooterMaxLines: CompactFooterMaxLines;
|
|
364
|
+
editorMetadataFormat: string;
|
|
365
|
+
separator: SeparatorStyle;
|
|
366
|
+
contextStyle: ContextStyle;
|
|
367
|
+
editorModelLabel: ModelLabelSource;
|
|
368
|
+
editorStyle: EditorStyle;
|
|
369
|
+
editorStyles: EditorStylesConfig;
|
|
370
|
+
editorBorderColorMode: EditorBorderColorMode;
|
|
371
|
+
contextThresholds: ContextThresholds;
|
|
372
|
+
pathDisplay: PathDisplayConfig;
|
|
373
|
+
gitBranch: GitBranchConfig;
|
|
374
|
+
colorSources: ColorSourcesConfig;
|
|
375
|
+
features: UiFeaturesConfig;
|
|
376
|
+
footerSegments: FooterSegmentsConfig;
|
|
377
|
+
gitCommit: GitCommitConfig;
|
|
378
|
+
gitMetrics: GitMetricsConfig;
|
|
379
|
+
extensionStatuses: ExtensionStatusesConfig;
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Canonical footer format variable names. In a `footerFormat` string these
|
|
384
|
+
* are written as `$name` or `${name}`.
|
|
385
|
+
*/
|
|
386
|
+
export const FOOTER_FORMAT_VARIABLES = [
|
|
387
|
+
"cwd",
|
|
388
|
+
"session_name",
|
|
389
|
+
"git_branch",
|
|
390
|
+
"git_status",
|
|
391
|
+
"git_state",
|
|
392
|
+
"runtime",
|
|
393
|
+
"model",
|
|
394
|
+
"provider",
|
|
395
|
+
"session_duration",
|
|
396
|
+
"username",
|
|
397
|
+
"os",
|
|
398
|
+
"time",
|
|
399
|
+
"context",
|
|
400
|
+
"tokens",
|
|
401
|
+
"cache_read",
|
|
402
|
+
"cache_write",
|
|
403
|
+
"cost",
|
|
404
|
+
"subscription",
|
|
405
|
+
"auto_compaction",
|
|
406
|
+
"package",
|
|
407
|
+
"package_version",
|
|
408
|
+
"git_commit",
|
|
409
|
+
"git_tag",
|
|
410
|
+
"git_metrics",
|
|
411
|
+
"git_added",
|
|
412
|
+
"git_deleted",
|
|
413
|
+
"sep",
|
|
414
|
+
] as const;
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Alias → canonical variable name mapping for `footerFormat`.
|
|
418
|
+
* `$fill` is special (not a variable) and handled by the parser.
|
|
419
|
+
*/
|
|
420
|
+
export const FOOTER_FORMAT_ALIASES: Record<string, string> = {
|
|
421
|
+
directory: "cwd",
|
|
422
|
+
branch: "git_branch",
|
|
423
|
+
status: "git_status",
|
|
424
|
+
state: "git_state",
|
|
425
|
+
commit: "git_commit",
|
|
426
|
+
tag: "git_tag",
|
|
427
|
+
duration: "session_duration",
|
|
428
|
+
separator: "sep",
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
/** Shared configuration file used by the pi-one-ui package. */
|
|
432
|
+
export const configPath = join(getAgentDir(), "pi-one-ui.json");
|
|
433
|
+
const legacyConfigPath = join(getAgentDir(), "pi-mine-ui.json");
|
|
434
|
+
const legacyZentuiConfigPath = join(getAgentDir(), "zentui.json");
|
|
435
|
+
|
|
436
|
+
const defaultFooterSegments: FooterSegmentsConfig = {
|
|
437
|
+
cwd: true,
|
|
438
|
+
sessionName: true,
|
|
439
|
+
gitBranch: true,
|
|
440
|
+
gitStatus: true,
|
|
441
|
+
gitCounts: false,
|
|
442
|
+
gitCommit: false,
|
|
443
|
+
gitMetrics: false,
|
|
444
|
+
runtime: true,
|
|
445
|
+
modelInfo: false,
|
|
446
|
+
context: true,
|
|
447
|
+
tokens: true,
|
|
448
|
+
cost: true,
|
|
449
|
+
sessionDuration: false,
|
|
450
|
+
username: false,
|
|
451
|
+
time: false,
|
|
452
|
+
os: false,
|
|
453
|
+
packageVersion: false,
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
const DEFAULT_COMPLETION_MENU: CompletionMenuStyle = "palette";
|
|
457
|
+
|
|
458
|
+
const defaultAccentRailStyle: AccentRailEditorStyleConfig = {
|
|
459
|
+
rail: "▎",
|
|
460
|
+
asciiRail: "|",
|
|
461
|
+
transparent: false,
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
const defaultMinimalistStyle: MinimalistEditorStyleConfig = {
|
|
465
|
+
pathDisplay: "compact",
|
|
466
|
+
contextFormat: "percent",
|
|
467
|
+
contextGauge: false,
|
|
468
|
+
showSessionName: true,
|
|
469
|
+
showTimer: true,
|
|
470
|
+
showCost: true,
|
|
471
|
+
showGit: true,
|
|
472
|
+
contextThresholds: { warning: 70, error: 90 },
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
const defaultStarshipStyle: StarshipFooterStyleConfig = {
|
|
476
|
+
format: "",
|
|
477
|
+
responsive: true,
|
|
478
|
+
compactFormat: DEFAULT_COMPACT_FOOTER_FORMAT,
|
|
479
|
+
compactMaxLines: 2,
|
|
480
|
+
separator: "pipe",
|
|
481
|
+
contextStyle: "text",
|
|
482
|
+
contextThresholds: { warning: 70, error: 90 },
|
|
483
|
+
pathDisplay: { mode: "basename", depth: 0 },
|
|
484
|
+
segments: defaultFooterSegments,
|
|
485
|
+
gitBranch: { maxLength: "full" },
|
|
486
|
+
gitCommit: { hashLength: 7, onlyDetached: true, showTag: true },
|
|
487
|
+
gitMetrics: { onlyNonzero: true, ignoreSubmodules: false },
|
|
488
|
+
extensionStatuses: {
|
|
489
|
+
defaultPlacement: "right",
|
|
490
|
+
placements: {},
|
|
491
|
+
colorModes: {},
|
|
492
|
+
},
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
const defaultComponents: ComponentsConfig = {
|
|
496
|
+
editor: {
|
|
497
|
+
enabled: true,
|
|
498
|
+
style: "opencode",
|
|
499
|
+
colorSource: "theme",
|
|
500
|
+
borderColorMode: "static",
|
|
501
|
+
modelLabel: "id",
|
|
502
|
+
viewportIndicators: true,
|
|
503
|
+
styles: {
|
|
504
|
+
opencode: {
|
|
505
|
+
metadataFormat: DEFAULT_EDITOR_METADATA_FORMAT,
|
|
506
|
+
completionMenu: DEFAULT_COMPLETION_MENU,
|
|
507
|
+
},
|
|
508
|
+
"opencode-copy-friendly": {
|
|
509
|
+
metadataFormat: DEFAULT_EDITOR_METADATA_FORMAT,
|
|
510
|
+
completionMenu: DEFAULT_COMPLETION_MENU,
|
|
511
|
+
},
|
|
512
|
+
"accent-rail": defaultAccentRailStyle,
|
|
513
|
+
minimalist: defaultMinimalistStyle,
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
userMessages: {
|
|
517
|
+
enabled: true,
|
|
518
|
+
style: "framed",
|
|
519
|
+
colorSource: "theme",
|
|
520
|
+
styles: {
|
|
521
|
+
framed: {},
|
|
522
|
+
"framed-copy-friendly": {},
|
|
523
|
+
compact: {},
|
|
524
|
+
labeled: {},
|
|
525
|
+
},
|
|
526
|
+
},
|
|
527
|
+
workingLine: {
|
|
528
|
+
enabled: false,
|
|
529
|
+
turnSummary: true,
|
|
530
|
+
spinner: "star-bloom",
|
|
531
|
+
spinnerIntervalMs: DEFAULT_WORKING_LINE_SPINNER_INTERVAL_MS,
|
|
532
|
+
animateSpinnerColor: false,
|
|
533
|
+
textIntervalMs: DEFAULT_WORKING_LINE_TEXT_INTERVAL_MS,
|
|
534
|
+
textAnimation: "classic",
|
|
535
|
+
colorSource: "theme",
|
|
536
|
+
messages: { custom: true, values: [...PI_WORKING_LINE_MESSAGES] },
|
|
537
|
+
segments: { tool: true, elapsed: true, thought: true, tokens: true },
|
|
538
|
+
},
|
|
539
|
+
selectorBorders: { enabled: true, style: "zentui", colorSource: "theme" },
|
|
540
|
+
footer: {
|
|
541
|
+
style: "starship",
|
|
542
|
+
colorSource: "theme",
|
|
543
|
+
modelLabel: "id",
|
|
544
|
+
styles: { starship: defaultStarshipStyle },
|
|
545
|
+
},
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
export const defaultConfig: PolishedTuiConfig = {
|
|
549
|
+
projectRefreshIntervalMs: DEFAULT_PROJECT_REFRESH_INTERVAL_MS,
|
|
550
|
+
icons: { mode: "auto", ...NERD_DEFAULT_ICONS },
|
|
551
|
+
colors: {
|
|
552
|
+
cwd: "bold cyan",
|
|
553
|
+
sessionName: "bold green",
|
|
554
|
+
gitBranch: "bold purple",
|
|
555
|
+
gitStatus: "bold red",
|
|
556
|
+
contextNormal: "bright-black",
|
|
557
|
+
contextWarning: "bold yellow",
|
|
558
|
+
contextError: "bold red",
|
|
559
|
+
tokens: "bright-black",
|
|
560
|
+
cost: "bold green",
|
|
561
|
+
separator: "bright-black",
|
|
562
|
+
runtimePrefix: "",
|
|
563
|
+
extensionStatus: "bright-black",
|
|
564
|
+
sessionDuration: "yellow",
|
|
565
|
+
packageVersion: "208",
|
|
566
|
+
gitCommit: "bold green",
|
|
567
|
+
gitMetricsAdded: "bold green",
|
|
568
|
+
gitMetricsDeleted: "bold red",
|
|
569
|
+
username: "bold yellow",
|
|
570
|
+
time: "bold yellow",
|
|
571
|
+
os: "bold white",
|
|
572
|
+
},
|
|
573
|
+
components: defaultComponents,
|
|
574
|
+
footerFormat: defaultStarshipStyle.format,
|
|
575
|
+
responsiveFooter: defaultStarshipStyle.responsive,
|
|
576
|
+
compactFooterFormat: defaultStarshipStyle.compactFormat,
|
|
577
|
+
compactFooterMaxLines: defaultStarshipStyle.compactMaxLines,
|
|
578
|
+
editorMetadataFormat: DEFAULT_EDITOR_METADATA_FORMAT,
|
|
579
|
+
separator: defaultStarshipStyle.separator,
|
|
580
|
+
contextStyle: defaultStarshipStyle.contextStyle,
|
|
581
|
+
editorModelLabel: defaultComponents.editor.modelLabel,
|
|
582
|
+
editorStyle: defaultComponents.editor.style,
|
|
583
|
+
editorStyles: { minimalist: defaultMinimalistStyle },
|
|
584
|
+
editorBorderColorMode: defaultComponents.editor.borderColorMode,
|
|
585
|
+
contextThresholds: defaultStarshipStyle.contextThresholds,
|
|
586
|
+
pathDisplay: defaultStarshipStyle.pathDisplay,
|
|
587
|
+
gitBranch: defaultStarshipStyle.gitBranch,
|
|
588
|
+
colorSources: { starship: "theme", editor: "theme", userMessages: "theme" },
|
|
589
|
+
features: {
|
|
590
|
+
editor: true,
|
|
591
|
+
statusLine: true,
|
|
592
|
+
viewportIndicators: true,
|
|
593
|
+
},
|
|
594
|
+
footerSegments: defaultFooterSegments,
|
|
595
|
+
gitCommit: defaultStarshipStyle.gitCommit,
|
|
596
|
+
gitMetrics: defaultStarshipStyle.gitMetrics,
|
|
597
|
+
extensionStatuses: defaultStarshipStyle.extensionStatuses,
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
type ConfigRecord = Record<string, unknown>;
|
|
601
|
+
|
|
602
|
+
function isRecord(value: unknown): value is ConfigRecord {
|
|
603
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function parseProjectRefreshIntervalMs(value: unknown): number {
|
|
607
|
+
if (value === 0) return 0;
|
|
608
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
609
|
+
return DEFAULT_PROJECT_REFRESH_INTERVAL_MS;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const interval = Math.round(value);
|
|
613
|
+
if (interval <= 0) return 0;
|
|
614
|
+
return Math.max(MIN_PROJECT_REFRESH_INTERVAL_MS, interval);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function clampPercent(value: number): number {
|
|
618
|
+
return Math.max(0, Math.min(100, value));
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function parseContextStyle(value: unknown): ContextStyle {
|
|
622
|
+
if (value === "text" || value === "gauge" || value === "text+gauge")
|
|
623
|
+
return value;
|
|
624
|
+
return defaultConfig.contextStyle;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function parseEditorModelLabel(
|
|
628
|
+
value: unknown,
|
|
629
|
+
fallback: ModelLabelSource = defaultComponents.editor.modelLabel,
|
|
630
|
+
): ModelLabelSource {
|
|
631
|
+
if (value === "id" || value === "name") return value;
|
|
632
|
+
return fallback;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function parseEditorStyle(value: unknown): EditorStyle {
|
|
636
|
+
if (
|
|
637
|
+
value === "opencode" ||
|
|
638
|
+
value === "opencode-copy-friendly" ||
|
|
639
|
+
value === "accent-rail" ||
|
|
640
|
+
value === "minimalist"
|
|
641
|
+
) {
|
|
642
|
+
return value;
|
|
643
|
+
}
|
|
644
|
+
if (value === "polished") return "opencode";
|
|
645
|
+
if (value === "polished-copy-friendly") return "opencode-copy-friendly";
|
|
646
|
+
return defaultConfig.editorStyle;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function parseEditorBorderColorMode(value: unknown): EditorBorderColorMode {
|
|
650
|
+
if (value === "static" || value === "adaptive") return value;
|
|
651
|
+
return defaultConfig.editorBorderColorMode;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function parseCompletionMenuStyle(value: unknown): CompletionMenuStyle {
|
|
655
|
+
return value === "native" || value === "palette"
|
|
656
|
+
? value
|
|
657
|
+
: DEFAULT_COMPLETION_MENU;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
export function isSeparatorStyle(value: unknown): value is SeparatorStyle {
|
|
661
|
+
return (
|
|
662
|
+
value === "pipe" ||
|
|
663
|
+
value === "dot" ||
|
|
664
|
+
value === "chevron" ||
|
|
665
|
+
value === "none"
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function parseSeparatorStyle(value: unknown): SeparatorStyle {
|
|
670
|
+
return isSeparatorStyle(value) ? value : defaultConfig.separator;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function parseContextThresholds(
|
|
674
|
+
value: unknown,
|
|
675
|
+
defaults: ContextThresholds = defaultStarshipStyle.contextThresholds,
|
|
676
|
+
): ContextThresholds {
|
|
677
|
+
if (!isRecord(value)) return { ...defaults };
|
|
678
|
+
|
|
679
|
+
const warningRaw = value.warning;
|
|
680
|
+
const errorRaw = value.error;
|
|
681
|
+
let warning =
|
|
682
|
+
typeof warningRaw === "number" && Number.isFinite(warningRaw)
|
|
683
|
+
? clampPercent(Math.round(warningRaw))
|
|
684
|
+
: defaults.warning;
|
|
685
|
+
let error =
|
|
686
|
+
typeof errorRaw === "number" && Number.isFinite(errorRaw)
|
|
687
|
+
? clampPercent(Math.round(errorRaw))
|
|
688
|
+
: defaults.error;
|
|
689
|
+
if (error < warning) {
|
|
690
|
+
const swapped = warning;
|
|
691
|
+
warning = error;
|
|
692
|
+
error = swapped;
|
|
693
|
+
}
|
|
694
|
+
return { warning, error };
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function parsePathDisplay(value: unknown): PathDisplayConfig {
|
|
698
|
+
const defaults = defaultConfig.pathDisplay;
|
|
699
|
+
if (!isRecord(value)) return { ...defaults };
|
|
700
|
+
const mode =
|
|
701
|
+
value.mode === "full" || value.mode === "basename"
|
|
702
|
+
? value.mode
|
|
703
|
+
: defaults.mode;
|
|
704
|
+
const rawDepth = value.depth;
|
|
705
|
+
const depth =
|
|
706
|
+
typeof rawDepth === "number" && Number.isFinite(rawDepth) && rawDepth >= 0
|
|
707
|
+
? Math.min(5, Math.floor(rawDepth))
|
|
708
|
+
: defaults.depth;
|
|
709
|
+
return { mode, depth };
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function normalizeGitBranchMaxLength(value: unknown): GitBranchMaxLength {
|
|
713
|
+
if (value === "full") return value;
|
|
714
|
+
if (typeof value === "number" && Number.isInteger(value) && value > 0)
|
|
715
|
+
return value;
|
|
716
|
+
return defaultConfig.gitBranch.maxLength;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function parseGitBranchConfig(value: unknown): GitBranchConfig {
|
|
720
|
+
const defaults = defaultConfig.gitBranch;
|
|
721
|
+
if (!isRecord(value)) return { ...defaults };
|
|
722
|
+
return {
|
|
723
|
+
maxLength: normalizeGitBranchMaxLength(value.maxLength),
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function stringValue(
|
|
728
|
+
record: Record<string, unknown>,
|
|
729
|
+
key: string,
|
|
730
|
+
): string | undefined {
|
|
731
|
+
const value = record[key];
|
|
732
|
+
return typeof value === "string" ? value : undefined;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function parseCompactFooterMaxLines(value: unknown): CompactFooterMaxLines {
|
|
736
|
+
return value === 1 || value === 2 || value === 3 || value === "unlimited"
|
|
737
|
+
? value
|
|
738
|
+
: 2;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function colorValue(
|
|
742
|
+
record: Record<string, unknown>,
|
|
743
|
+
key: string,
|
|
744
|
+
): string | undefined {
|
|
745
|
+
const value = stringValue(record, key);
|
|
746
|
+
return value !== undefined && isSupportedColorSpec(value) ? value : undefined;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function definedColors(
|
|
750
|
+
colors: Partial<
|
|
751
|
+
Record<keyof PolishedTuiConfig["colors"], string | undefined>
|
|
752
|
+
>,
|
|
753
|
+
): Partial<PolishedTuiConfig["colors"]> {
|
|
754
|
+
return Object.fromEntries(
|
|
755
|
+
Object.entries(colors).filter(
|
|
756
|
+
(entry): entry is [keyof PolishedTuiConfig["colors"], string] =>
|
|
757
|
+
typeof entry[1] === "string",
|
|
758
|
+
),
|
|
759
|
+
) as Partial<PolishedTuiConfig["colors"]>;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function normalizeIconOverrides(
|
|
763
|
+
record: Record<string, unknown>,
|
|
764
|
+
): Partial<IconGlyphs> {
|
|
765
|
+
return Object.fromEntries(
|
|
766
|
+
ICON_GLYPH_KEYS.flatMap((key) => {
|
|
767
|
+
const value = stringValue(record, key);
|
|
768
|
+
return value === undefined ? [] : [[key, value]];
|
|
769
|
+
}),
|
|
770
|
+
) as Partial<IconGlyphs>;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function normalizeColors(
|
|
774
|
+
record: Record<string, unknown>,
|
|
775
|
+
): Partial<PolishedTuiConfig["colors"]> {
|
|
776
|
+
return definedColors({
|
|
777
|
+
cwd: colorValue(record, "cwd") ?? colorValue(record, "cwdText"),
|
|
778
|
+
sessionName: colorValue(record, "sessionName"),
|
|
779
|
+
gitBranch: colorValue(record, "gitBranch") ?? colorValue(record, "git"),
|
|
780
|
+
gitStatus: colorValue(record, "gitStatus"),
|
|
781
|
+
contextNormal: colorValue(record, "contextNormal"),
|
|
782
|
+
contextWarning: colorValue(record, "contextWarning"),
|
|
783
|
+
contextError: colorValue(record, "contextError"),
|
|
784
|
+
tokens: colorValue(record, "tokens"),
|
|
785
|
+
cost: colorValue(record, "cost"),
|
|
786
|
+
separator: colorValue(record, "separator"),
|
|
787
|
+
runtimePrefix: colorValue(record, "runtimePrefix"),
|
|
788
|
+
extensionStatus: colorValue(record, "extensionStatus"),
|
|
789
|
+
sessionDuration: colorValue(record, "sessionDuration"),
|
|
790
|
+
packageVersion: colorValue(record, "packageVersion"),
|
|
791
|
+
gitCommit: colorValue(record, "gitCommit"),
|
|
792
|
+
gitMetricsAdded: colorValue(record, "gitMetricsAdded"),
|
|
793
|
+
gitMetricsDeleted: colorValue(record, "gitMetricsDeleted"),
|
|
794
|
+
username: colorValue(record, "username"),
|
|
795
|
+
time: colorValue(record, "time"),
|
|
796
|
+
os: colorValue(record, "os"),
|
|
797
|
+
editorAccent: colorValue(record, "editorAccent"),
|
|
798
|
+
editorRail: colorValue(record, "editorRail"),
|
|
799
|
+
editorPrompt: colorValue(record, "editorPrompt"),
|
|
800
|
+
editorBorder: colorValue(record, "editorBorder"),
|
|
801
|
+
editorGitBranch: colorValue(record, "editorGitBranch"),
|
|
802
|
+
editorModel: colorValue(record, "editorModel"),
|
|
803
|
+
editorProvider: colorValue(record, "editorProvider"),
|
|
804
|
+
editorThinking: colorValue(record, "editorThinking"),
|
|
805
|
+
editorThinkingMinimal: colorValue(record, "editorThinkingMinimal"),
|
|
806
|
+
editorThinkingLow: colorValue(record, "editorThinkingLow"),
|
|
807
|
+
editorThinkingMedium: colorValue(record, "editorThinkingMedium"),
|
|
808
|
+
editorThinkingHigh: colorValue(record, "editorThinkingHigh"),
|
|
809
|
+
editorThinkingXhigh: colorValue(record, "editorThinkingXhigh"),
|
|
810
|
+
editorThinkingMax: colorValue(record, "editorThinkingMax"),
|
|
811
|
+
workingLineLow: colorValue(record, "workingLineLow"),
|
|
812
|
+
workingLineMid: colorValue(record, "workingLineMid"),
|
|
813
|
+
workingLineHigh: colorValue(record, "workingLineHigh"),
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/** Clamp hashLength to Git's valid abbreviation range [4, 40]. */
|
|
818
|
+
function normalizeGitHashLength(value: unknown): number {
|
|
819
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
820
|
+
if (!Number.isFinite(parsed)) return defaultConfig.gitCommit.hashLength;
|
|
821
|
+
const rounded = Math.round(parsed);
|
|
822
|
+
return Math.min(40, Math.max(4, rounded));
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function normalizeGitCommitConfig(
|
|
826
|
+
record: Record<string, unknown>,
|
|
827
|
+
): GitCommitConfig {
|
|
828
|
+
return {
|
|
829
|
+
hashLength: normalizeGitHashLength(record.hashLength),
|
|
830
|
+
onlyDetached:
|
|
831
|
+
typeof record.onlyDetached === "boolean"
|
|
832
|
+
? record.onlyDetached
|
|
833
|
+
: defaultConfig.gitCommit.onlyDetached,
|
|
834
|
+
showTag:
|
|
835
|
+
typeof record.showTag === "boolean"
|
|
836
|
+
? record.showTag
|
|
837
|
+
: defaultConfig.gitCommit.showTag,
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function normalizeGitMetricsConfig(
|
|
842
|
+
record: Record<string, unknown>,
|
|
843
|
+
): GitMetricsConfig {
|
|
844
|
+
return {
|
|
845
|
+
onlyNonzero:
|
|
846
|
+
typeof record.onlyNonzero === "boolean"
|
|
847
|
+
? record.onlyNonzero
|
|
848
|
+
: defaultConfig.gitMetrics.onlyNonzero,
|
|
849
|
+
ignoreSubmodules:
|
|
850
|
+
typeof record.ignoreSubmodules === "boolean"
|
|
851
|
+
? record.ignoreSubmodules
|
|
852
|
+
: defaultConfig.gitMetrics.ignoreSubmodules,
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
export function isExtensionStatusPlacement(
|
|
857
|
+
value: unknown,
|
|
858
|
+
): value is ExtensionStatusPlacement {
|
|
859
|
+
return (
|
|
860
|
+
value === "off" ||
|
|
861
|
+
value === "left" ||
|
|
862
|
+
value === "middle" ||
|
|
863
|
+
value === "right"
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
export function isExtensionStatusColorMode(
|
|
868
|
+
value: unknown,
|
|
869
|
+
): value is ExtensionStatusColorMode {
|
|
870
|
+
return value === "zentui" || value === "original";
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function normalizeExtensionStatuses(
|
|
874
|
+
record: Record<string, unknown>,
|
|
875
|
+
): ExtensionStatusesConfig {
|
|
876
|
+
const defaultPlacement = isExtensionStatusPlacement(record.defaultPlacement)
|
|
877
|
+
? record.defaultPlacement
|
|
878
|
+
: defaultConfig.extensionStatuses.defaultPlacement;
|
|
879
|
+
const placements = isRecord(record.placements)
|
|
880
|
+
? Object.fromEntries(
|
|
881
|
+
Object.entries(record.placements).filter(
|
|
882
|
+
(entry): entry is [string, ExtensionStatusPlacement] =>
|
|
883
|
+
isExtensionStatusPlacement(entry[1]),
|
|
884
|
+
),
|
|
885
|
+
)
|
|
886
|
+
: {};
|
|
887
|
+
const colorModes = isRecord(record.colorModes)
|
|
888
|
+
? Object.fromEntries(
|
|
889
|
+
Object.entries(record.colorModes).filter(
|
|
890
|
+
(entry): entry is [string, ExtensionStatusColorMode] =>
|
|
891
|
+
isExtensionStatusColorMode(entry[1]),
|
|
892
|
+
),
|
|
893
|
+
)
|
|
894
|
+
: {};
|
|
895
|
+
|
|
896
|
+
return {
|
|
897
|
+
defaultPlacement,
|
|
898
|
+
placements,
|
|
899
|
+
colorModes,
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function isColorSourceKey(value: string): value is keyof ColorSourcesConfig {
|
|
904
|
+
return value === "starship" || value === "editor" || value === "userMessages";
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function isUiFeatureKey(value: string): value is keyof UiFeaturesConfig {
|
|
908
|
+
return (
|
|
909
|
+
value === "editor" ||
|
|
910
|
+
value === "statusLine" ||
|
|
911
|
+
value === "viewportIndicators"
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function isFooterSegmentKey(
|
|
916
|
+
value: string,
|
|
917
|
+
): value is keyof FooterSegmentsConfig {
|
|
918
|
+
return (
|
|
919
|
+
value === "cwd" ||
|
|
920
|
+
value === "sessionName" ||
|
|
921
|
+
value === "gitBranch" ||
|
|
922
|
+
value === "gitStatus" ||
|
|
923
|
+
value === "gitCounts" ||
|
|
924
|
+
value === "runtime" ||
|
|
925
|
+
value === "modelInfo" ||
|
|
926
|
+
value === "context" ||
|
|
927
|
+
value === "tokens" ||
|
|
928
|
+
value === "cost" ||
|
|
929
|
+
value === "sessionDuration" ||
|
|
930
|
+
value === "username" ||
|
|
931
|
+
value === "time" ||
|
|
932
|
+
value === "os" ||
|
|
933
|
+
value === "packageVersion" ||
|
|
934
|
+
value === "gitCommit" ||
|
|
935
|
+
value === "gitMetrics"
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function validColorSourceEntries(
|
|
940
|
+
record: Record<string, unknown>,
|
|
941
|
+
): Partial<ColorSourcesConfig> {
|
|
942
|
+
return Object.fromEntries(
|
|
943
|
+
Object.entries(record).filter(
|
|
944
|
+
(entry): entry is [keyof ColorSourcesConfig, ColorSource] => {
|
|
945
|
+
const [key, value] = entry;
|
|
946
|
+
return (
|
|
947
|
+
isColorSourceKey(key) && (value === "theme" || value === "terminal")
|
|
948
|
+
);
|
|
949
|
+
},
|
|
950
|
+
),
|
|
951
|
+
) as Partial<ColorSourcesConfig>;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function validUiFeatureEntries(
|
|
955
|
+
record: Record<string, unknown>,
|
|
956
|
+
): Partial<UiFeaturesConfig> {
|
|
957
|
+
return Object.fromEntries(
|
|
958
|
+
Object.entries(record).filter(
|
|
959
|
+
(entry): entry is [keyof UiFeaturesConfig, boolean] => {
|
|
960
|
+
const [key, value] = entry;
|
|
961
|
+
return isUiFeatureKey(key) && typeof value === "boolean";
|
|
962
|
+
},
|
|
963
|
+
),
|
|
964
|
+
) as Partial<UiFeaturesConfig>;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function validFooterSegmentEntries(
|
|
968
|
+
record: Record<string, unknown>,
|
|
969
|
+
): Partial<FooterSegmentsConfig> {
|
|
970
|
+
return Object.fromEntries(
|
|
971
|
+
Object.entries(record).filter(
|
|
972
|
+
(entry): entry is [keyof FooterSegmentsConfig, boolean] => {
|
|
973
|
+
const [key, value] = entry;
|
|
974
|
+
return isFooterSegmentKey(key) && typeof value === "boolean";
|
|
975
|
+
},
|
|
976
|
+
),
|
|
977
|
+
) as Partial<FooterSegmentsConfig>;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
type ConfigFileState =
|
|
981
|
+
| { kind: "missing"; record: ConfigRecord; writePath: string }
|
|
982
|
+
| { kind: "valid"; record: ConfigRecord; writePath: string; mode: number }
|
|
983
|
+
| { kind: "corrupt"; error: unknown };
|
|
984
|
+
|
|
985
|
+
function errorCode(error: unknown): string | undefined {
|
|
986
|
+
return typeof error === "object" && error !== null && "code" in error
|
|
987
|
+
? String(error.code)
|
|
988
|
+
: undefined;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
function readConfigFileState(path: string): ConfigFileState {
|
|
992
|
+
let writePath = path;
|
|
993
|
+
try {
|
|
994
|
+
const pathStat = lstatSync(path);
|
|
995
|
+
if (pathStat.isSymbolicLink()) writePath = realpathSync(path);
|
|
996
|
+
const targetStat = statSync(writePath);
|
|
997
|
+
const parsed = JSON.parse(readFileSync(writePath, "utf8"));
|
|
998
|
+
return isRecord(parsed)
|
|
999
|
+
? {
|
|
1000
|
+
kind: "valid",
|
|
1001
|
+
record: parsed,
|
|
1002
|
+
writePath,
|
|
1003
|
+
mode: targetStat.mode & 0o7777,
|
|
1004
|
+
}
|
|
1005
|
+
: {
|
|
1006
|
+
kind: "corrupt",
|
|
1007
|
+
error: new Error("top-level value must be a JSON object"),
|
|
1008
|
+
};
|
|
1009
|
+
} catch (error) {
|
|
1010
|
+
if (errorCode(error) === "ENOENT") {
|
|
1011
|
+
try {
|
|
1012
|
+
lstatSync(path);
|
|
1013
|
+
} catch (pathError) {
|
|
1014
|
+
if (errorCode(pathError) === "ENOENT")
|
|
1015
|
+
return { kind: "missing", record: {}, writePath: path };
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return { kind: "corrupt", error };
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
function writeConfigAtomically(
|
|
1023
|
+
path: string,
|
|
1024
|
+
record: ConfigRecord,
|
|
1025
|
+
mode?: number,
|
|
1026
|
+
): void {
|
|
1027
|
+
const tempPath = join(
|
|
1028
|
+
dirname(path),
|
|
1029
|
+
`.${basename(path)}.${process.pid}.${randomUUID()}.tmp`,
|
|
1030
|
+
);
|
|
1031
|
+
let file: number | undefined;
|
|
1032
|
+
try {
|
|
1033
|
+
file = openSync(tempPath, "wx", mode ?? 0o666);
|
|
1034
|
+
if (mode !== undefined) fchmodSync(file, mode);
|
|
1035
|
+
writeFileSync(file, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
1036
|
+
fsyncSync(file);
|
|
1037
|
+
closeSync(file);
|
|
1038
|
+
file = undefined;
|
|
1039
|
+
renameSync(tempPath, path);
|
|
1040
|
+
} catch (error) {
|
|
1041
|
+
if (file !== undefined) {
|
|
1042
|
+
try {
|
|
1043
|
+
closeSync(file);
|
|
1044
|
+
} catch {}
|
|
1045
|
+
}
|
|
1046
|
+
try {
|
|
1047
|
+
unlinkSync(tempPath);
|
|
1048
|
+
} catch (cleanupError) {
|
|
1049
|
+
if (errorCode(cleanupError) !== "ENOENT") {
|
|
1050
|
+
// Preserve the persistence failure; the best-effort cleanup error is secondary.
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
throw error;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function mutateConfig(
|
|
1058
|
+
path: string,
|
|
1059
|
+
mutate: (record: ConfigRecord) => void,
|
|
1060
|
+
): PolishedTuiConfig {
|
|
1061
|
+
const state = readConfigFileState(path);
|
|
1062
|
+
if (state.kind === "corrupt") {
|
|
1063
|
+
const detail =
|
|
1064
|
+
state.error instanceof Error ? ` (${state.error.message})` : "";
|
|
1065
|
+
throw new Error(
|
|
1066
|
+
`Refusing to save Zentui config because ${path} is corrupt or unreadable; fix or remove it first.${detail}`,
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
mutate(state.record);
|
|
1070
|
+
writeConfigAtomically(
|
|
1071
|
+
state.writePath,
|
|
1072
|
+
state.record,
|
|
1073
|
+
state.kind === "valid" ? state.mode : undefined,
|
|
1074
|
+
);
|
|
1075
|
+
return mergeConfig(state.record);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
export function ensureConfigExists(_path = configPath): void {
|
|
1079
|
+
// Intentionally left as a no-op. Zentui config is user-owned and
|
|
1080
|
+
// compatibility-sensitive: runtime defaults come from `mergeConfig({})`, and
|
|
1081
|
+
// the extension should not persist opinionated defaults unless the user
|
|
1082
|
+
// explicitly changes a setting.
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
function hasOwn(record: ConfigRecord, key: string): boolean {
|
|
1086
|
+
return Object.hasOwn(record, key);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
function recordValue(value: unknown): ConfigRecord {
|
|
1090
|
+
return isRecord(value) ? value : {};
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
function resolvedValue(
|
|
1094
|
+
canonical: ConfigRecord,
|
|
1095
|
+
canonicalKey: string,
|
|
1096
|
+
legacy?: ConfigRecord,
|
|
1097
|
+
legacyKey = canonicalKey,
|
|
1098
|
+
): unknown {
|
|
1099
|
+
if (hasOwn(canonical, canonicalKey)) return canonical[canonicalKey];
|
|
1100
|
+
if (legacy && hasOwn(legacy, legacyKey)) return legacy[legacyKey];
|
|
1101
|
+
return undefined;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
function parseBoolean(value: unknown, fallback: boolean): boolean {
|
|
1105
|
+
return typeof value === "boolean" ? value : fallback;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
function parseColorSource(value: unknown, fallback: ColorSource): ColorSource {
|
|
1109
|
+
return value === "theme" || value === "terminal" ? value : fallback;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
function parseSelectorBorderStyle(value: unknown): SelectorBorderStyle {
|
|
1113
|
+
return value === "zentui" ? value : "zentui";
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
function parseFooterStyle(value: unknown): FooterStyle | undefined {
|
|
1117
|
+
return value === "native" || value === "starship" || value === "hidden"
|
|
1118
|
+
? value
|
|
1119
|
+
: undefined;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
function parseNonEmptyString(value: unknown, fallback: string): string {
|
|
1123
|
+
return typeof value === "string" && value.length > 0 ? value : fallback;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function resolveContextThresholds(
|
|
1127
|
+
canonical: unknown,
|
|
1128
|
+
legacy: unknown,
|
|
1129
|
+
defaults: ContextThresholds,
|
|
1130
|
+
): ContextThresholds {
|
|
1131
|
+
const canonicalRecord = recordValue(canonical);
|
|
1132
|
+
const legacyRecord = recordValue(legacy);
|
|
1133
|
+
return parseContextThresholds(
|
|
1134
|
+
{
|
|
1135
|
+
warning: resolvedValue(canonicalRecord, "warning", legacyRecord),
|
|
1136
|
+
error: resolvedValue(canonicalRecord, "error", legacyRecord),
|
|
1137
|
+
},
|
|
1138
|
+
defaults,
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
function resolvePathDisplay(
|
|
1143
|
+
canonical: unknown,
|
|
1144
|
+
legacy: unknown,
|
|
1145
|
+
): PathDisplayConfig {
|
|
1146
|
+
const canonicalRecord = recordValue(canonical);
|
|
1147
|
+
const legacyRecord = recordValue(legacy);
|
|
1148
|
+
return parsePathDisplay({
|
|
1149
|
+
mode: resolvedValue(canonicalRecord, "mode", legacyRecord),
|
|
1150
|
+
depth: resolvedValue(canonicalRecord, "depth", legacyRecord),
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function resolveGitBranch(
|
|
1155
|
+
canonical: unknown,
|
|
1156
|
+
legacy: unknown,
|
|
1157
|
+
): GitBranchConfig {
|
|
1158
|
+
const canonicalRecord = recordValue(canonical);
|
|
1159
|
+
const legacyRecord = recordValue(legacy);
|
|
1160
|
+
return parseGitBranchConfig({
|
|
1161
|
+
maxLength: resolvedValue(canonicalRecord, "maxLength", legacyRecord),
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
function resolveGitCommit(
|
|
1166
|
+
canonical: unknown,
|
|
1167
|
+
legacy: unknown,
|
|
1168
|
+
): GitCommitConfig {
|
|
1169
|
+
const canonicalRecord = recordValue(canonical);
|
|
1170
|
+
const legacyRecord = recordValue(legacy);
|
|
1171
|
+
return normalizeGitCommitConfig({
|
|
1172
|
+
hashLength: resolvedValue(canonicalRecord, "hashLength", legacyRecord),
|
|
1173
|
+
onlyDetached: resolvedValue(canonicalRecord, "onlyDetached", legacyRecord),
|
|
1174
|
+
showTag: resolvedValue(canonicalRecord, "showTag", legacyRecord),
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
function resolveGitMetrics(
|
|
1179
|
+
canonical: unknown,
|
|
1180
|
+
legacy: unknown,
|
|
1181
|
+
): GitMetricsConfig {
|
|
1182
|
+
const canonicalRecord = recordValue(canonical);
|
|
1183
|
+
const legacyRecord = recordValue(legacy);
|
|
1184
|
+
return normalizeGitMetricsConfig({
|
|
1185
|
+
onlyNonzero: resolvedValue(canonicalRecord, "onlyNonzero", legacyRecord),
|
|
1186
|
+
ignoreSubmodules: resolvedValue(
|
|
1187
|
+
canonicalRecord,
|
|
1188
|
+
"ignoreSubmodules",
|
|
1189
|
+
legacyRecord,
|
|
1190
|
+
),
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
function resolveExtensionStatuses(
|
|
1195
|
+
canonical: unknown,
|
|
1196
|
+
legacy: unknown,
|
|
1197
|
+
): ExtensionStatusesConfig {
|
|
1198
|
+
const canonicalRecord = recordValue(canonical);
|
|
1199
|
+
const legacyRecord = recordValue(legacy);
|
|
1200
|
+
return normalizeExtensionStatuses({
|
|
1201
|
+
defaultPlacement: resolvedValue(
|
|
1202
|
+
canonicalRecord,
|
|
1203
|
+
"defaultPlacement",
|
|
1204
|
+
legacyRecord,
|
|
1205
|
+
),
|
|
1206
|
+
placements: resolvedValue(canonicalRecord, "placements", legacyRecord),
|
|
1207
|
+
colorModes: resolvedValue(canonicalRecord, "colorModes", legacyRecord),
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
const FOOTER_SEGMENT_KEYS = Object.keys(defaultFooterSegments) as Array<
|
|
1212
|
+
keyof FooterSegmentsConfig
|
|
1213
|
+
>;
|
|
1214
|
+
|
|
1215
|
+
function resolveFooterSegments(
|
|
1216
|
+
canonical: unknown,
|
|
1217
|
+
legacy: unknown,
|
|
1218
|
+
): FooterSegmentsConfig {
|
|
1219
|
+
const canonicalRecord = recordValue(canonical);
|
|
1220
|
+
const legacyRecord = recordValue(legacy);
|
|
1221
|
+
return Object.fromEntries(
|
|
1222
|
+
FOOTER_SEGMENT_KEYS.map((key) => [
|
|
1223
|
+
key,
|
|
1224
|
+
parseBoolean(
|
|
1225
|
+
resolvedValue(canonicalRecord, key, legacyRecord),
|
|
1226
|
+
defaultFooterSegments[key],
|
|
1227
|
+
),
|
|
1228
|
+
]),
|
|
1229
|
+
) as FooterSegmentsConfig;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
function legacyCopyFriendly(record: ConfigRecord): boolean {
|
|
1233
|
+
return record.copyFriendly === true;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
function resolveEditorStyle(
|
|
1237
|
+
editor: ConfigRecord,
|
|
1238
|
+
polished: ConfigRecord,
|
|
1239
|
+
features: ConfigRecord,
|
|
1240
|
+
): EditorStyle {
|
|
1241
|
+
const rawStyle = editor.style;
|
|
1242
|
+
if (
|
|
1243
|
+
rawStyle === "opencode" ||
|
|
1244
|
+
rawStyle === "opencode-copy-friendly" ||
|
|
1245
|
+
rawStyle === "accent-rail" ||
|
|
1246
|
+
rawStyle === "minimalist"
|
|
1247
|
+
) {
|
|
1248
|
+
return rawStyle;
|
|
1249
|
+
}
|
|
1250
|
+
if (rawStyle === "polished-copy-friendly") return "opencode-copy-friendly";
|
|
1251
|
+
if (rawStyle === "polished") {
|
|
1252
|
+
return hasOwn(polished, "copyFriendly") && legacyCopyFriendly(polished)
|
|
1253
|
+
? "opencode-copy-friendly"
|
|
1254
|
+
: "opencode";
|
|
1255
|
+
}
|
|
1256
|
+
if (hasOwn(polished, "copyFriendly")) {
|
|
1257
|
+
return legacyCopyFriendly(polished) ? "opencode-copy-friendly" : "opencode";
|
|
1258
|
+
}
|
|
1259
|
+
return legacyCopyFriendly(features) ? "opencode-copy-friendly" : "opencode";
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
function resolveFooterStyle(
|
|
1263
|
+
footer: ConfigRecord,
|
|
1264
|
+
features: ConfigRecord,
|
|
1265
|
+
): FooterStyle {
|
|
1266
|
+
const explicit = parseFooterStyle(footer.style);
|
|
1267
|
+
if (explicit) return explicit;
|
|
1268
|
+
if (typeof footer.enabled === "boolean")
|
|
1269
|
+
return footer.enabled ? "starship" : "native";
|
|
1270
|
+
if (typeof features.statusLine === "boolean") {
|
|
1271
|
+
return features.statusLine ? "starship" : "native";
|
|
1272
|
+
}
|
|
1273
|
+
return defaultComponents.footer.style;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
function resolveUserMessagesSelection(
|
|
1277
|
+
userMessages: ConfigRecord,
|
|
1278
|
+
framed: ConfigRecord,
|
|
1279
|
+
features: ConfigRecord,
|
|
1280
|
+
): Pick<UserMessagesComponentConfig, "enabled" | "style"> {
|
|
1281
|
+
const normalEnabled = parseBoolean(
|
|
1282
|
+
resolvedValue(userMessages, "enabled", features, "editor"),
|
|
1283
|
+
defaultComponents.userMessages.enabled,
|
|
1284
|
+
);
|
|
1285
|
+
const rawStyle = userMessages.style;
|
|
1286
|
+
if (
|
|
1287
|
+
rawStyle === "compact" ||
|
|
1288
|
+
rawStyle === "labeled" ||
|
|
1289
|
+
rawStyle === "framed-copy-friendly"
|
|
1290
|
+
) {
|
|
1291
|
+
return { style: rawStyle, enabled: normalEnabled };
|
|
1292
|
+
}
|
|
1293
|
+
if (rawStyle === "framed" || hasOwn(framed, "copyFriendly")) {
|
|
1294
|
+
return {
|
|
1295
|
+
style: legacyCopyFriendly(framed) ? "framed-copy-friendly" : "framed",
|
|
1296
|
+
enabled: normalEnabled,
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
return {
|
|
1300
|
+
style: legacyCopyFriendly(features) ? "framed-copy-friendly" : "framed",
|
|
1301
|
+
enabled: normalEnabled,
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
function resolveWorkingLineMessages(
|
|
1306
|
+
messages: ConfigRecord,
|
|
1307
|
+
): WorkingLineMessagesConfig {
|
|
1308
|
+
const preset = () => [...PI_WORKING_LINE_MESSAGES];
|
|
1309
|
+
const hasCanonicalCustom = hasOwn(messages, "custom");
|
|
1310
|
+
const custom = hasCanonicalCustom
|
|
1311
|
+
? parseBoolean(messages.custom, true)
|
|
1312
|
+
: messages.mode !== "native";
|
|
1313
|
+
if (hasCanonicalCustom) {
|
|
1314
|
+
return {
|
|
1315
|
+
custom,
|
|
1316
|
+
values: hasOwn(messages, "values")
|
|
1317
|
+
? normalizeWorkingLineMessages(messages.values)
|
|
1318
|
+
: preset(),
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
const legacyValues = normalizeWorkingLineMessages(messages.values);
|
|
1322
|
+
if (messages.mode === "append") {
|
|
1323
|
+
return {
|
|
1324
|
+
custom,
|
|
1325
|
+
values: normalizeWorkingLineMessages([...preset(), ...legacyValues]),
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
if (messages.mode === "replace" || messages.mode === "native") {
|
|
1329
|
+
return {
|
|
1330
|
+
custom,
|
|
1331
|
+
values: legacyValues.length > 0 ? legacyValues : preset(),
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
return {
|
|
1335
|
+
custom,
|
|
1336
|
+
values: hasOwn(messages, "values") ? legacyValues : preset(),
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
function resolveComponents(config: ConfigRecord): ComponentsConfig {
|
|
1341
|
+
const components = recordValue(config.components);
|
|
1342
|
+
const editor = recordValue(components.editor);
|
|
1343
|
+
const editorStyles = recordValue(editor.styles);
|
|
1344
|
+
const opencode = recordValue(editorStyles.opencode);
|
|
1345
|
+
const polished = recordValue(editorStyles.polished);
|
|
1346
|
+
const opencodeCopyFriendly = recordValue(
|
|
1347
|
+
editorStyles["opencode-copy-friendly"],
|
|
1348
|
+
);
|
|
1349
|
+
const polishedCopyFriendly = recordValue(
|
|
1350
|
+
editorStyles["polished-copy-friendly"],
|
|
1351
|
+
);
|
|
1352
|
+
const accentRail = recordValue(editorStyles["accent-rail"]);
|
|
1353
|
+
const minimalist = recordValue(editorStyles.minimalist);
|
|
1354
|
+
const userMessages = recordValue(components.userMessages);
|
|
1355
|
+
const userMessageStyles = recordValue(userMessages.styles);
|
|
1356
|
+
const framed = recordValue(userMessageStyles.framed);
|
|
1357
|
+
const workingLine = recordValue(components.workingLine);
|
|
1358
|
+
const workingLineMessages = recordValue(workingLine.messages);
|
|
1359
|
+
const workingLineSegments = recordValue(workingLine.segments);
|
|
1360
|
+
const selectorBorders = recordValue(components.selectorBorders);
|
|
1361
|
+
const footer = recordValue(components.footer);
|
|
1362
|
+
const footerStyles = recordValue(footer.styles);
|
|
1363
|
+
const starship = recordValue(footerStyles.starship);
|
|
1364
|
+
const features = recordValue(config.features);
|
|
1365
|
+
const colorSources = recordValue(config.colorSources);
|
|
1366
|
+
|
|
1367
|
+
const minimalistThresholds = resolveContextThresholds(
|
|
1368
|
+
minimalist.contextThresholds,
|
|
1369
|
+
config.contextThresholds,
|
|
1370
|
+
defaultMinimalistStyle.contextThresholds,
|
|
1371
|
+
);
|
|
1372
|
+
const footerThresholds = resolveContextThresholds(
|
|
1373
|
+
starship.contextThresholds,
|
|
1374
|
+
config.contextThresholds,
|
|
1375
|
+
defaultStarshipStyle.contextThresholds,
|
|
1376
|
+
);
|
|
1377
|
+
const compactFormat = resolvedValue(
|
|
1378
|
+
starship,
|
|
1379
|
+
"compactFormat",
|
|
1380
|
+
config,
|
|
1381
|
+
"compactFooterFormat",
|
|
1382
|
+
);
|
|
1383
|
+
const metadataFormat = hasOwn(opencode, "metadataFormat")
|
|
1384
|
+
? opencode.metadataFormat
|
|
1385
|
+
: resolvedValue(polished, "metadataFormat", config, "editorMetadataFormat");
|
|
1386
|
+
const lowRailMetadataFormat = hasOwn(opencodeCopyFriendly, "metadataFormat")
|
|
1387
|
+
? opencodeCopyFriendly.metadataFormat
|
|
1388
|
+
: hasOwn(polishedCopyFriendly, "metadataFormat")
|
|
1389
|
+
? polishedCopyFriendly.metadataFormat
|
|
1390
|
+
: metadataFormat;
|
|
1391
|
+
const userMessagesSelection = resolveUserMessagesSelection(
|
|
1392
|
+
userMessages,
|
|
1393
|
+
framed,
|
|
1394
|
+
features,
|
|
1395
|
+
);
|
|
1396
|
+
|
|
1397
|
+
return {
|
|
1398
|
+
editor: {
|
|
1399
|
+
enabled: parseBoolean(
|
|
1400
|
+
resolvedValue(editor, "enabled", features, "editor"),
|
|
1401
|
+
defaultComponents.editor.enabled,
|
|
1402
|
+
),
|
|
1403
|
+
style: resolveEditorStyle(editor, polished, features),
|
|
1404
|
+
colorSource: parseColorSource(
|
|
1405
|
+
resolvedValue(editor, "colorSource", colorSources, "editor"),
|
|
1406
|
+
defaultComponents.editor.colorSource,
|
|
1407
|
+
),
|
|
1408
|
+
borderColorMode: parseEditorBorderColorMode(
|
|
1409
|
+
resolvedValue(
|
|
1410
|
+
editor,
|
|
1411
|
+
"borderColorMode",
|
|
1412
|
+
config,
|
|
1413
|
+
"editorBorderColorMode",
|
|
1414
|
+
),
|
|
1415
|
+
),
|
|
1416
|
+
modelLabel: parseEditorModelLabel(
|
|
1417
|
+
resolvedValue(editor, "modelLabel", config, "editorModelLabel"),
|
|
1418
|
+
defaultComponents.editor.modelLabel,
|
|
1419
|
+
),
|
|
1420
|
+
viewportIndicators: parseBoolean(
|
|
1421
|
+
resolvedValue(
|
|
1422
|
+
editor,
|
|
1423
|
+
"viewportIndicators",
|
|
1424
|
+
features,
|
|
1425
|
+
"viewportIndicators",
|
|
1426
|
+
),
|
|
1427
|
+
defaultComponents.editor.viewportIndicators,
|
|
1428
|
+
),
|
|
1429
|
+
styles: {
|
|
1430
|
+
opencode: {
|
|
1431
|
+
metadataFormat: parseNonEmptyString(
|
|
1432
|
+
metadataFormat,
|
|
1433
|
+
DEFAULT_EDITOR_METADATA_FORMAT,
|
|
1434
|
+
),
|
|
1435
|
+
completionMenu: parseCompletionMenuStyle(opencode.completionMenu),
|
|
1436
|
+
},
|
|
1437
|
+
"opencode-copy-friendly": {
|
|
1438
|
+
metadataFormat: parseNonEmptyString(
|
|
1439
|
+
lowRailMetadataFormat,
|
|
1440
|
+
DEFAULT_EDITOR_METADATA_FORMAT,
|
|
1441
|
+
),
|
|
1442
|
+
completionMenu: parseCompletionMenuStyle(
|
|
1443
|
+
opencodeCopyFriendly.completionMenu,
|
|
1444
|
+
),
|
|
1445
|
+
},
|
|
1446
|
+
"accent-rail": {
|
|
1447
|
+
rail: parseNonEmptyString(
|
|
1448
|
+
accentRail.rail,
|
|
1449
|
+
defaultAccentRailStyle.rail,
|
|
1450
|
+
),
|
|
1451
|
+
asciiRail: parseNonEmptyString(
|
|
1452
|
+
accentRail.asciiRail,
|
|
1453
|
+
defaultAccentRailStyle.asciiRail,
|
|
1454
|
+
),
|
|
1455
|
+
transparent: parseBoolean(
|
|
1456
|
+
accentRail.transparent,
|
|
1457
|
+
defaultAccentRailStyle.transparent,
|
|
1458
|
+
),
|
|
1459
|
+
},
|
|
1460
|
+
minimalist: {
|
|
1461
|
+
pathDisplay:
|
|
1462
|
+
minimalist.pathDisplay === "compact" ||
|
|
1463
|
+
minimalist.pathDisplay === "project" ||
|
|
1464
|
+
minimalist.pathDisplay === "full"
|
|
1465
|
+
? minimalist.pathDisplay
|
|
1466
|
+
: defaultMinimalistStyle.pathDisplay,
|
|
1467
|
+
contextFormat:
|
|
1468
|
+
minimalist.contextFormat === "percent" ||
|
|
1469
|
+
minimalist.contextFormat === "percent-total"
|
|
1470
|
+
? minimalist.contextFormat
|
|
1471
|
+
: defaultMinimalistStyle.contextFormat,
|
|
1472
|
+
contextGauge: parseBoolean(
|
|
1473
|
+
minimalist.contextGauge,
|
|
1474
|
+
defaultMinimalistStyle.contextGauge,
|
|
1475
|
+
),
|
|
1476
|
+
showSessionName: parseBoolean(
|
|
1477
|
+
minimalist.showSessionName,
|
|
1478
|
+
defaultMinimalistStyle.showSessionName,
|
|
1479
|
+
),
|
|
1480
|
+
showTimer: parseBoolean(
|
|
1481
|
+
minimalist.showTimer,
|
|
1482
|
+
defaultMinimalistStyle.showTimer,
|
|
1483
|
+
),
|
|
1484
|
+
showCost: parseBoolean(
|
|
1485
|
+
minimalist.showCost,
|
|
1486
|
+
defaultMinimalistStyle.showCost,
|
|
1487
|
+
),
|
|
1488
|
+
showGit: parseBoolean(
|
|
1489
|
+
minimalist.showGit,
|
|
1490
|
+
defaultMinimalistStyle.showGit,
|
|
1491
|
+
),
|
|
1492
|
+
contextThresholds: minimalistThresholds,
|
|
1493
|
+
},
|
|
1494
|
+
},
|
|
1495
|
+
},
|
|
1496
|
+
userMessages: {
|
|
1497
|
+
enabled: userMessagesSelection.enabled,
|
|
1498
|
+
style: userMessagesSelection.style,
|
|
1499
|
+
colorSource: parseColorSource(
|
|
1500
|
+
resolvedValue(
|
|
1501
|
+
userMessages,
|
|
1502
|
+
"colorSource",
|
|
1503
|
+
colorSources,
|
|
1504
|
+
"userMessages",
|
|
1505
|
+
),
|
|
1506
|
+
defaultComponents.userMessages.colorSource,
|
|
1507
|
+
),
|
|
1508
|
+
styles: {
|
|
1509
|
+
framed: {},
|
|
1510
|
+
"framed-copy-friendly": {},
|
|
1511
|
+
compact: {},
|
|
1512
|
+
labeled: {},
|
|
1513
|
+
},
|
|
1514
|
+
},
|
|
1515
|
+
workingLine: {
|
|
1516
|
+
enabled: parseBoolean(
|
|
1517
|
+
workingLine.enabled,
|
|
1518
|
+
defaultComponents.workingLine.enabled,
|
|
1519
|
+
),
|
|
1520
|
+
turnSummary: parseBoolean(
|
|
1521
|
+
workingLine.turnSummary,
|
|
1522
|
+
defaultComponents.workingLine.turnSummary,
|
|
1523
|
+
),
|
|
1524
|
+
spinner:
|
|
1525
|
+
workingLine.spinner === "braille" ||
|
|
1526
|
+
workingLine.spinner === "star-bloom" ||
|
|
1527
|
+
workingLine.spinner === "pinwheel" ||
|
|
1528
|
+
workingLine.spinner === "claude-inspired" ||
|
|
1529
|
+
workingLine.spinner === "pulse"
|
|
1530
|
+
? workingLine.spinner
|
|
1531
|
+
: defaultComponents.workingLine.spinner,
|
|
1532
|
+
spinnerIntervalMs: isValidWorkingLineIntervalMs(
|
|
1533
|
+
resolvedValue(
|
|
1534
|
+
workingLine,
|
|
1535
|
+
"spinnerIntervalMs",
|
|
1536
|
+
workingLine,
|
|
1537
|
+
"intervalMs",
|
|
1538
|
+
),
|
|
1539
|
+
)
|
|
1540
|
+
? (resolvedValue(
|
|
1541
|
+
workingLine,
|
|
1542
|
+
"spinnerIntervalMs",
|
|
1543
|
+
workingLine,
|
|
1544
|
+
"intervalMs",
|
|
1545
|
+
) as number)
|
|
1546
|
+
: defaultComponents.workingLine.spinnerIntervalMs,
|
|
1547
|
+
animateSpinnerColor: parseBoolean(
|
|
1548
|
+
workingLine.animateSpinnerColor,
|
|
1549
|
+
defaultComponents.workingLine.animateSpinnerColor,
|
|
1550
|
+
),
|
|
1551
|
+
textIntervalMs: isValidWorkingLineIntervalMs(workingLine.textIntervalMs)
|
|
1552
|
+
? workingLine.textIntervalMs
|
|
1553
|
+
: defaultComponents.workingLine.textIntervalMs,
|
|
1554
|
+
textAnimation:
|
|
1555
|
+
workingLine.textAnimation === "classic" ||
|
|
1556
|
+
workingLine.textAnimation === "kitt" ||
|
|
1557
|
+
workingLine.textAnimation === "disabled"
|
|
1558
|
+
? workingLine.textAnimation
|
|
1559
|
+
: defaultComponents.workingLine.textAnimation,
|
|
1560
|
+
colorSource: parseColorSource(
|
|
1561
|
+
workingLine.colorSource,
|
|
1562
|
+
defaultComponents.workingLine.colorSource,
|
|
1563
|
+
),
|
|
1564
|
+
messages: resolveWorkingLineMessages(workingLineMessages),
|
|
1565
|
+
segments: {
|
|
1566
|
+
tool: parseBoolean(
|
|
1567
|
+
workingLineSegments.tool,
|
|
1568
|
+
defaultComponents.workingLine.segments.tool,
|
|
1569
|
+
),
|
|
1570
|
+
elapsed: parseBoolean(
|
|
1571
|
+
workingLineSegments.elapsed,
|
|
1572
|
+
defaultComponents.workingLine.segments.elapsed,
|
|
1573
|
+
),
|
|
1574
|
+
thought: parseBoolean(
|
|
1575
|
+
workingLineSegments.thought,
|
|
1576
|
+
defaultComponents.workingLine.segments.thought,
|
|
1577
|
+
),
|
|
1578
|
+
tokens: parseBoolean(
|
|
1579
|
+
workingLineSegments.tokens,
|
|
1580
|
+
defaultComponents.workingLine.segments.tokens,
|
|
1581
|
+
),
|
|
1582
|
+
},
|
|
1583
|
+
},
|
|
1584
|
+
selectorBorders: {
|
|
1585
|
+
enabled: parseBoolean(
|
|
1586
|
+
resolvedValue(selectorBorders, "enabled", features, "editor"),
|
|
1587
|
+
defaultComponents.selectorBorders.enabled,
|
|
1588
|
+
),
|
|
1589
|
+
style: parseSelectorBorderStyle(resolvedValue(selectorBorders, "style")),
|
|
1590
|
+
colorSource: parseColorSource(
|
|
1591
|
+
resolvedValue(selectorBorders, "colorSource", colorSources, "editor"),
|
|
1592
|
+
defaultComponents.selectorBorders.colorSource,
|
|
1593
|
+
),
|
|
1594
|
+
},
|
|
1595
|
+
footer: {
|
|
1596
|
+
style: resolveFooterStyle(footer, features),
|
|
1597
|
+
colorSource: parseColorSource(
|
|
1598
|
+
resolvedValue(footer, "colorSource", colorSources, "starship"),
|
|
1599
|
+
defaultComponents.footer.colorSource,
|
|
1600
|
+
),
|
|
1601
|
+
modelLabel: parseEditorModelLabel(
|
|
1602
|
+
resolvedValue(footer, "modelLabel", config, "editorModelLabel"),
|
|
1603
|
+
defaultComponents.footer.modelLabel,
|
|
1604
|
+
),
|
|
1605
|
+
styles: {
|
|
1606
|
+
starship: {
|
|
1607
|
+
format:
|
|
1608
|
+
typeof resolvedValue(starship, "format", config, "footerFormat") ===
|
|
1609
|
+
"string"
|
|
1610
|
+
? (resolvedValue(
|
|
1611
|
+
starship,
|
|
1612
|
+
"format",
|
|
1613
|
+
config,
|
|
1614
|
+
"footerFormat",
|
|
1615
|
+
) as string)
|
|
1616
|
+
: defaultStarshipStyle.format,
|
|
1617
|
+
responsive: parseBoolean(
|
|
1618
|
+
resolvedValue(starship, "responsive", config, "responsiveFooter"),
|
|
1619
|
+
defaultStarshipStyle.responsive,
|
|
1620
|
+
),
|
|
1621
|
+
compactFormat: parseNonEmptyString(
|
|
1622
|
+
compactFormat,
|
|
1623
|
+
defaultStarshipStyle.compactFormat,
|
|
1624
|
+
),
|
|
1625
|
+
compactMaxLines: parseCompactFooterMaxLines(
|
|
1626
|
+
resolvedValue(
|
|
1627
|
+
starship,
|
|
1628
|
+
"compactMaxLines",
|
|
1629
|
+
config,
|
|
1630
|
+
"compactFooterMaxLines",
|
|
1631
|
+
),
|
|
1632
|
+
),
|
|
1633
|
+
separator: parseSeparatorStyle(
|
|
1634
|
+
resolvedValue(starship, "separator", config, "separator"),
|
|
1635
|
+
),
|
|
1636
|
+
contextStyle: parseContextStyle(
|
|
1637
|
+
resolvedValue(starship, "contextStyle", config, "contextStyle"),
|
|
1638
|
+
),
|
|
1639
|
+
contextThresholds: footerThresholds,
|
|
1640
|
+
pathDisplay: resolvePathDisplay(
|
|
1641
|
+
starship.pathDisplay,
|
|
1642
|
+
config.pathDisplay,
|
|
1643
|
+
),
|
|
1644
|
+
segments: resolveFooterSegments(
|
|
1645
|
+
starship.segments,
|
|
1646
|
+
config.footerSegments,
|
|
1647
|
+
),
|
|
1648
|
+
gitBranch: resolveGitBranch(starship.gitBranch, config.gitBranch),
|
|
1649
|
+
gitCommit: resolveGitCommit(starship.gitCommit, config.gitCommit),
|
|
1650
|
+
gitMetrics: resolveGitMetrics(starship.gitMetrics, config.gitMetrics),
|
|
1651
|
+
extensionStatuses: resolveExtensionStatuses(
|
|
1652
|
+
starship.extensionStatuses,
|
|
1653
|
+
config.extensionStatuses,
|
|
1654
|
+
),
|
|
1655
|
+
},
|
|
1656
|
+
},
|
|
1657
|
+
},
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
function compatibilityView(config: ZentuiConfig): PolishedTuiConfig {
|
|
1662
|
+
const starship = config.components.footer.styles.starship;
|
|
1663
|
+
const minimalist = config.components.editor.styles.minimalist;
|
|
1664
|
+
return {
|
|
1665
|
+
...config,
|
|
1666
|
+
footerFormat: starship.format,
|
|
1667
|
+
responsiveFooter: starship.responsive,
|
|
1668
|
+
compactFooterFormat: starship.compactFormat,
|
|
1669
|
+
compactFooterMaxLines: starship.compactMaxLines,
|
|
1670
|
+
separator: starship.separator,
|
|
1671
|
+
contextStyle: starship.contextStyle,
|
|
1672
|
+
contextThresholds: starship.contextThresholds,
|
|
1673
|
+
pathDisplay: starship.pathDisplay,
|
|
1674
|
+
gitBranch: starship.gitBranch,
|
|
1675
|
+
footerSegments: starship.segments,
|
|
1676
|
+
gitCommit: starship.gitCommit,
|
|
1677
|
+
gitMetrics: starship.gitMetrics,
|
|
1678
|
+
extensionStatuses: starship.extensionStatuses,
|
|
1679
|
+
editorStyle: config.components.editor.style,
|
|
1680
|
+
editorStyles: { minimalist },
|
|
1681
|
+
editorMetadataFormat:
|
|
1682
|
+
config.components.editor.styles.opencode.metadataFormat,
|
|
1683
|
+
editorBorderColorMode: config.components.editor.borderColorMode,
|
|
1684
|
+
editorModelLabel: config.components.editor.modelLabel,
|
|
1685
|
+
colorSources: {
|
|
1686
|
+
starship: config.components.footer.colorSource,
|
|
1687
|
+
editor: config.components.editor.colorSource,
|
|
1688
|
+
userMessages: config.components.userMessages.colorSource,
|
|
1689
|
+
},
|
|
1690
|
+
features: {
|
|
1691
|
+
editor: config.components.editor.enabled,
|
|
1692
|
+
statusLine: config.components.footer.style === "starship",
|
|
1693
|
+
viewportIndicators: config.components.editor.viewportIndicators,
|
|
1694
|
+
},
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
const unsupportedComponentStyles = new WeakMap<
|
|
1699
|
+
ZentuiConfig,
|
|
1700
|
+
ReadonlySet<ComponentStyleOwner>
|
|
1701
|
+
>();
|
|
1702
|
+
|
|
1703
|
+
const knownComponentStyleIds: Record<
|
|
1704
|
+
ComponentStyleOwner,
|
|
1705
|
+
ReadonlySet<string>
|
|
1706
|
+
> = {
|
|
1707
|
+
editor: new Set([
|
|
1708
|
+
"opencode",
|
|
1709
|
+
"opencode-copy-friendly",
|
|
1710
|
+
"accent-rail",
|
|
1711
|
+
"minimalist",
|
|
1712
|
+
"polished",
|
|
1713
|
+
"polished-copy-friendly",
|
|
1714
|
+
]),
|
|
1715
|
+
userMessages: new Set([
|
|
1716
|
+
"framed",
|
|
1717
|
+
"framed-copy-friendly",
|
|
1718
|
+
"compact",
|
|
1719
|
+
"labeled",
|
|
1720
|
+
]),
|
|
1721
|
+
selectorBorders: new Set(["zentui"]),
|
|
1722
|
+
footer: new Set(["native", "starship", "hidden"]),
|
|
1723
|
+
};
|
|
1724
|
+
|
|
1725
|
+
function unsupportedSelectedStyleId(
|
|
1726
|
+
record: ConfigRecord,
|
|
1727
|
+
owner: ComponentStyleOwner,
|
|
1728
|
+
): string | undefined {
|
|
1729
|
+
const component = recordValue(recordValue(record.components)[owner]);
|
|
1730
|
+
if (!hasOwn(component, "style")) return undefined;
|
|
1731
|
+
const style = component.style;
|
|
1732
|
+
return typeof style === "string" &&
|
|
1733
|
+
style.trim() &&
|
|
1734
|
+
!knownComponentStyleIds[owner].has(style)
|
|
1735
|
+
? style
|
|
1736
|
+
: undefined;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
export function hasUnsupportedComponentStyle(
|
|
1740
|
+
config: ZentuiConfig,
|
|
1741
|
+
owner: ComponentStyleOwner,
|
|
1742
|
+
): boolean {
|
|
1743
|
+
return unsupportedComponentStyles.get(config)?.has(owner) ?? false;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
export function mergeConfig(parsed: unknown): PolishedTuiConfig {
|
|
1747
|
+
const config = isRecord(parsed) ? parsed : {};
|
|
1748
|
+
const iconsRecord = recordValue(config.icons);
|
|
1749
|
+
const colorsRecord = recordValue(config.colors);
|
|
1750
|
+
const colors = normalizeColors(colorsRecord);
|
|
1751
|
+
const canonical: ZentuiConfig = {
|
|
1752
|
+
projectRefreshIntervalMs: parseProjectRefreshIntervalMs(
|
|
1753
|
+
config.projectRefreshIntervalMs,
|
|
1754
|
+
),
|
|
1755
|
+
icons: resolveConfiguredIcons(
|
|
1756
|
+
normalizeIconMode(iconsRecord.mode),
|
|
1757
|
+
normalizeIconOverrides(iconsRecord),
|
|
1758
|
+
),
|
|
1759
|
+
colors: {
|
|
1760
|
+
...defaultConfig.colors,
|
|
1761
|
+
...colors,
|
|
1762
|
+
...(colors.editorGitBranch === undefined && colors.gitBranch !== undefined
|
|
1763
|
+
? { editorGitBranch: colors.gitBranch }
|
|
1764
|
+
: {}),
|
|
1765
|
+
},
|
|
1766
|
+
components: resolveComponents(config),
|
|
1767
|
+
};
|
|
1768
|
+
const view = compatibilityView(canonical);
|
|
1769
|
+
const unsupported = new Set<ComponentStyleOwner>();
|
|
1770
|
+
for (const owner of [
|
|
1771
|
+
"editor",
|
|
1772
|
+
"userMessages",
|
|
1773
|
+
"selectorBorders",
|
|
1774
|
+
"footer",
|
|
1775
|
+
] as const) {
|
|
1776
|
+
if (unsupportedSelectedStyleId(config, owner) !== undefined)
|
|
1777
|
+
unsupported.add(owner);
|
|
1778
|
+
}
|
|
1779
|
+
if (unsupported.size > 0) unsupportedComponentStyles.set(view, unsupported);
|
|
1780
|
+
return view;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
export function getExtensionStatusPlacement(
|
|
1784
|
+
config: ZentuiConfig,
|
|
1785
|
+
key: string,
|
|
1786
|
+
): ExtensionStatusPlacement {
|
|
1787
|
+
const statuses = config.components.footer.styles.starship.extensionStatuses;
|
|
1788
|
+
if (Object.hasOwn(statuses.placements, key)) {
|
|
1789
|
+
const placement = statuses.placements[key];
|
|
1790
|
+
if (isExtensionStatusPlacement(placement)) return placement;
|
|
1791
|
+
}
|
|
1792
|
+
return isExtensionStatusPlacement(statuses.defaultPlacement)
|
|
1793
|
+
? statuses.defaultPlacement
|
|
1794
|
+
: DEFAULT_EXTENSION_STATUS_PLACEMENT;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
export function getExtensionStatusColorMode(
|
|
1798
|
+
config: ZentuiConfig,
|
|
1799
|
+
key: string,
|
|
1800
|
+
): ExtensionStatusColorMode {
|
|
1801
|
+
const colorModes =
|
|
1802
|
+
config.components.footer.styles.starship.extensionStatuses.colorModes;
|
|
1803
|
+
if (Object.hasOwn(colorModes, key)) {
|
|
1804
|
+
const colorMode = colorModes[key];
|
|
1805
|
+
if (isExtensionStatusColorMode(colorMode)) return colorMode;
|
|
1806
|
+
}
|
|
1807
|
+
return DEFAULT_EXTENSION_STATUS_COLOR_MODE;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
export function loadConfig(): PolishedTuiConfig {
|
|
1811
|
+
try {
|
|
1812
|
+
let raw: string | undefined;
|
|
1813
|
+
if (existsSync(configPath)) {
|
|
1814
|
+
raw = readFileSync(configPath, "utf8");
|
|
1815
|
+
} else if (existsSync(legacyConfigPath)) {
|
|
1816
|
+
raw = readFileSync(legacyConfigPath, "utf8");
|
|
1817
|
+
try {
|
|
1818
|
+
writeFileSync(configPath, raw);
|
|
1819
|
+
} catch {
|
|
1820
|
+
// Keep using the legacy file in memory if migration cannot be written.
|
|
1821
|
+
}
|
|
1822
|
+
} else if (existsSync(legacyZentuiConfigPath)) {
|
|
1823
|
+
raw = readFileSync(legacyZentuiConfigPath, "utf8");
|
|
1824
|
+
try {
|
|
1825
|
+
writeFileSync(configPath, raw);
|
|
1826
|
+
} catch {
|
|
1827
|
+
// Keep using the legacy file in memory if migration cannot be written.
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
return mergeConfig(raw ? JSON.parse(raw) : {});
|
|
1831
|
+
} catch {
|
|
1832
|
+
return mergeConfig({});
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
function overlayKnown(raw: unknown, known: unknown): unknown {
|
|
1837
|
+
if (!isRecord(known)) return known;
|
|
1838
|
+
const output: ConfigRecord = isRecord(raw) ? { ...raw } : {};
|
|
1839
|
+
for (const [key, value] of Object.entries(known)) {
|
|
1840
|
+
Object.defineProperty(output, key, {
|
|
1841
|
+
value: overlayKnown(output[key], value),
|
|
1842
|
+
enumerable: true,
|
|
1843
|
+
configurable: true,
|
|
1844
|
+
writable: true,
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
return output;
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
type PreservedStyleIds = Partial<Record<ComponentStyleOwner, string>>;
|
|
1851
|
+
|
|
1852
|
+
function unknownSelectedStyleIds(record: ConfigRecord): PreservedStyleIds {
|
|
1853
|
+
return {
|
|
1854
|
+
editor: unsupportedSelectedStyleId(record, "editor"),
|
|
1855
|
+
userMessages: unsupportedSelectedStyleId(record, "userMessages"),
|
|
1856
|
+
selectorBorders: unsupportedSelectedStyleId(record, "selectorBorders"),
|
|
1857
|
+
footer: unsupportedSelectedStyleId(record, "footer"),
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
function restoreUnknownSelectedStyleIds(
|
|
1862
|
+
record: ConfigRecord,
|
|
1863
|
+
preserved: PreservedStyleIds,
|
|
1864
|
+
replacedStyle?: ComponentStyleOwner,
|
|
1865
|
+
): void {
|
|
1866
|
+
const components = recordValue(record.components);
|
|
1867
|
+
for (const [owner, style] of Object.entries(preserved) as [
|
|
1868
|
+
ComponentStyleOwner,
|
|
1869
|
+
string,
|
|
1870
|
+
][]) {
|
|
1871
|
+
if (style === undefined || owner === replacedStyle) continue;
|
|
1872
|
+
const component = recordValue(components[owner]);
|
|
1873
|
+
component.style = style;
|
|
1874
|
+
components[owner] = component;
|
|
1875
|
+
}
|
|
1876
|
+
record.components = components;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
function saveComponentsMutation(
|
|
1880
|
+
update: (components: ComponentsConfig) => void,
|
|
1881
|
+
path: string,
|
|
1882
|
+
cleanupRaw?: (record: ConfigRecord) => void,
|
|
1883
|
+
replacedStyle?: ComponentStyleOwner,
|
|
1884
|
+
): PolishedTuiConfig {
|
|
1885
|
+
return mutateConfig(path, (record) => {
|
|
1886
|
+
const preservedStyles = unknownSelectedStyleIds(record);
|
|
1887
|
+
const components = mergeConfig(record).components;
|
|
1888
|
+
update(components);
|
|
1889
|
+
const normalized = resolveComponents({ components });
|
|
1890
|
+
record.components = overlayKnown(record.components, normalized);
|
|
1891
|
+
restoreUnknownSelectedStyleIds(record, preservedStyles, replacedStyle);
|
|
1892
|
+
cleanupRaw?.(record);
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
function deleteLegacyEditorCopyFriendly(record: ConfigRecord): void {
|
|
1897
|
+
const components = record.components;
|
|
1898
|
+
if (!isRecord(components)) return;
|
|
1899
|
+
const editor = components.editor;
|
|
1900
|
+
if (!isRecord(editor)) return;
|
|
1901
|
+
const styles = editor.styles;
|
|
1902
|
+
if (!isRecord(styles)) return;
|
|
1903
|
+
const polished = styles.polished;
|
|
1904
|
+
if (isRecord(polished)) delete polished.copyFriendly;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
function deleteLegacyFooterEnabled(record: ConfigRecord): void {
|
|
1908
|
+
const components = record.components;
|
|
1909
|
+
if (!isRecord(components)) return;
|
|
1910
|
+
const footer = components.footer;
|
|
1911
|
+
if (isRecord(footer)) delete footer.enabled;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
function deleteLegacyMessageCopyFriendly(record: ConfigRecord): void {
|
|
1915
|
+
const components = record.components;
|
|
1916
|
+
if (!isRecord(components)) return;
|
|
1917
|
+
const userMessages = components.userMessages;
|
|
1918
|
+
if (!isRecord(userMessages)) return;
|
|
1919
|
+
const styles = userMessages.styles;
|
|
1920
|
+
if (!isRecord(styles)) return;
|
|
1921
|
+
const framed = styles.framed;
|
|
1922
|
+
if (isRecord(framed)) delete framed.copyFriendly;
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
function applyEditorComponentPatch(
|
|
1926
|
+
component: EditorComponentConfig,
|
|
1927
|
+
patch: Partial<
|
|
1928
|
+
Pick<
|
|
1929
|
+
EditorComponentConfig,
|
|
1930
|
+
| "enabled"
|
|
1931
|
+
| "style"
|
|
1932
|
+
| "colorSource"
|
|
1933
|
+
| "borderColorMode"
|
|
1934
|
+
| "modelLabel"
|
|
1935
|
+
| "viewportIndicators"
|
|
1936
|
+
>
|
|
1937
|
+
>,
|
|
1938
|
+
): void {
|
|
1939
|
+
if (patch.enabled !== undefined) component.enabled = patch.enabled;
|
|
1940
|
+
if (patch.style !== undefined) component.style = patch.style;
|
|
1941
|
+
if (patch.colorSource !== undefined)
|
|
1942
|
+
component.colorSource = patch.colorSource;
|
|
1943
|
+
if (patch.borderColorMode !== undefined)
|
|
1944
|
+
component.borderColorMode = patch.borderColorMode;
|
|
1945
|
+
if (patch.modelLabel !== undefined) component.modelLabel = patch.modelLabel;
|
|
1946
|
+
if (patch.viewportIndicators !== undefined) {
|
|
1947
|
+
component.viewportIndicators = patch.viewportIndicators;
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
export function saveEditorComponentPatch(
|
|
1952
|
+
patch: Partial<
|
|
1953
|
+
Pick<
|
|
1954
|
+
EditorComponentConfig,
|
|
1955
|
+
| "enabled"
|
|
1956
|
+
| "style"
|
|
1957
|
+
| "colorSource"
|
|
1958
|
+
| "borderColorMode"
|
|
1959
|
+
| "modelLabel"
|
|
1960
|
+
| "viewportIndicators"
|
|
1961
|
+
>
|
|
1962
|
+
>,
|
|
1963
|
+
path = configPath,
|
|
1964
|
+
): PolishedTuiConfig {
|
|
1965
|
+
return saveComponentsMutation(
|
|
1966
|
+
(components) => applyEditorComponentPatch(components.editor, patch),
|
|
1967
|
+
path,
|
|
1968
|
+
patch.style !== undefined ? deleteLegacyEditorCopyFriendly : undefined,
|
|
1969
|
+
patch.style !== undefined ? "editor" : undefined,
|
|
1970
|
+
);
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
export function savePolishedEditorStylePatch(
|
|
1974
|
+
patch: Partial<PolishedEditorStyleConfig>,
|
|
1975
|
+
path = configPath,
|
|
1976
|
+
): PolishedTuiConfig {
|
|
1977
|
+
return saveComponentsMutation((components) => {
|
|
1978
|
+
const style = components.editor.styles.opencode;
|
|
1979
|
+
if (patch.metadataFormat !== undefined)
|
|
1980
|
+
style.metadataFormat = patch.metadataFormat;
|
|
1981
|
+
if (patch.completionMenu !== undefined)
|
|
1982
|
+
style.completionMenu = patch.completionMenu;
|
|
1983
|
+
}, path);
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
export function savePolishedCopyFriendlyEditorStylePatch(
|
|
1987
|
+
patch: Partial<PolishedCopyFriendlyEditorStyleConfig>,
|
|
1988
|
+
path = configPath,
|
|
1989
|
+
): PolishedTuiConfig {
|
|
1990
|
+
return saveComponentsMutation((components) => {
|
|
1991
|
+
const style = components.editor.styles["opencode-copy-friendly"];
|
|
1992
|
+
if (patch.metadataFormat !== undefined)
|
|
1993
|
+
style.metadataFormat = patch.metadataFormat;
|
|
1994
|
+
if (patch.completionMenu !== undefined)
|
|
1995
|
+
style.completionMenu = patch.completionMenu;
|
|
1996
|
+
}, path);
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
export function saveAccentRailEditorStylePatch(
|
|
2000
|
+
patch: Partial<AccentRailEditorStyleConfig>,
|
|
2001
|
+
path = configPath,
|
|
2002
|
+
): PolishedTuiConfig {
|
|
2003
|
+
return saveComponentsMutation((components) => {
|
|
2004
|
+
const style = components.editor.styles["accent-rail"];
|
|
2005
|
+
if (patch.rail !== undefined) style.rail = patch.rail;
|
|
2006
|
+
if (patch.asciiRail !== undefined) style.asciiRail = patch.asciiRail;
|
|
2007
|
+
if (patch.transparent !== undefined) style.transparent = patch.transparent;
|
|
2008
|
+
}, path);
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
function applyMinimalistStylePatch(
|
|
2012
|
+
style: MinimalistEditorStyleConfig,
|
|
2013
|
+
patch: Partial<MinimalistEditorStyleConfig>,
|
|
2014
|
+
): void {
|
|
2015
|
+
if (patch.pathDisplay !== undefined) style.pathDisplay = patch.pathDisplay;
|
|
2016
|
+
if (patch.contextFormat !== undefined)
|
|
2017
|
+
style.contextFormat = patch.contextFormat;
|
|
2018
|
+
if (patch.contextGauge !== undefined) style.contextGauge = patch.contextGauge;
|
|
2019
|
+
if (patch.showSessionName !== undefined)
|
|
2020
|
+
style.showSessionName = patch.showSessionName;
|
|
2021
|
+
if (patch.showTimer !== undefined) style.showTimer = patch.showTimer;
|
|
2022
|
+
if (patch.showCost !== undefined) style.showCost = patch.showCost;
|
|
2023
|
+
if (patch.showGit !== undefined) style.showGit = patch.showGit;
|
|
2024
|
+
if (patch.contextThresholds !== undefined) {
|
|
2025
|
+
style.contextThresholds = {
|
|
2026
|
+
...style.contextThresholds,
|
|
2027
|
+
...patch.contextThresholds,
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
export function saveMinimalistEditorStylePatch(
|
|
2033
|
+
patch: Partial<MinimalistEditorStyleConfig>,
|
|
2034
|
+
path = configPath,
|
|
2035
|
+
): PolishedTuiConfig {
|
|
2036
|
+
return saveComponentsMutation(
|
|
2037
|
+
(components) =>
|
|
2038
|
+
applyMinimalistStylePatch(components.editor.styles.minimalist, patch),
|
|
2039
|
+
path,
|
|
2040
|
+
);
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
export function saveUserMessagesComponentPatch(
|
|
2044
|
+
patch: Partial<
|
|
2045
|
+
Pick<UserMessagesComponentConfig, "enabled" | "style" | "colorSource">
|
|
2046
|
+
>,
|
|
2047
|
+
path = configPath,
|
|
2048
|
+
): PolishedTuiConfig {
|
|
2049
|
+
return saveComponentsMutation(
|
|
2050
|
+
(components) => {
|
|
2051
|
+
const component = components.userMessages;
|
|
2052
|
+
if (patch.enabled !== undefined) component.enabled = patch.enabled;
|
|
2053
|
+
if (patch.style !== undefined) component.style = patch.style;
|
|
2054
|
+
if (patch.colorSource !== undefined)
|
|
2055
|
+
component.colorSource = patch.colorSource;
|
|
2056
|
+
},
|
|
2057
|
+
path,
|
|
2058
|
+
patch.style !== undefined ? deleteLegacyMessageCopyFriendly : undefined,
|
|
2059
|
+
patch.style !== undefined ? "userMessages" : undefined,
|
|
2060
|
+
);
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
export function saveWorkingLineComponentPatch(
|
|
2064
|
+
patch: WorkingLineComponentPatch,
|
|
2065
|
+
path = configPath,
|
|
2066
|
+
): PolishedTuiConfig {
|
|
2067
|
+
return saveComponentsMutation(
|
|
2068
|
+
(components) => {
|
|
2069
|
+
const component = components.workingLine;
|
|
2070
|
+
if (patch.enabled !== undefined) component.enabled = patch.enabled;
|
|
2071
|
+
if (patch.turnSummary !== undefined)
|
|
2072
|
+
component.turnSummary = patch.turnSummary;
|
|
2073
|
+
if (patch.spinner !== undefined) component.spinner = patch.spinner;
|
|
2074
|
+
if (patch.spinnerIntervalMs !== undefined)
|
|
2075
|
+
component.spinnerIntervalMs = patch.spinnerIntervalMs;
|
|
2076
|
+
if (patch.animateSpinnerColor !== undefined)
|
|
2077
|
+
component.animateSpinnerColor = patch.animateSpinnerColor;
|
|
2078
|
+
if (patch.textIntervalMs !== undefined)
|
|
2079
|
+
component.textIntervalMs = patch.textIntervalMs;
|
|
2080
|
+
if (patch.textAnimation !== undefined)
|
|
2081
|
+
component.textAnimation = patch.textAnimation;
|
|
2082
|
+
if (patch.colorSource !== undefined)
|
|
2083
|
+
component.colorSource = patch.colorSource;
|
|
2084
|
+
if (patch.messages?.custom !== undefined)
|
|
2085
|
+
component.messages.custom = patch.messages.custom;
|
|
2086
|
+
if (patch.messages?.values !== undefined) {
|
|
2087
|
+
component.messages.values = normalizeWorkingLineMessages([
|
|
2088
|
+
...patch.messages.values,
|
|
2089
|
+
]);
|
|
2090
|
+
}
|
|
2091
|
+
if (patch.segments?.tool !== undefined)
|
|
2092
|
+
component.segments.tool = patch.segments.tool;
|
|
2093
|
+
if (patch.segments?.elapsed !== undefined)
|
|
2094
|
+
component.segments.elapsed = patch.segments.elapsed;
|
|
2095
|
+
if (patch.segments?.thought !== undefined)
|
|
2096
|
+
component.segments.thought = patch.segments.thought;
|
|
2097
|
+
if (patch.segments?.tokens !== undefined)
|
|
2098
|
+
component.segments.tokens = patch.segments.tokens;
|
|
2099
|
+
},
|
|
2100
|
+
path,
|
|
2101
|
+
(record) => {
|
|
2102
|
+
const workingLine = recordValue(
|
|
2103
|
+
recordValue(record.components).workingLine,
|
|
2104
|
+
);
|
|
2105
|
+
delete workingLine.intervalMs;
|
|
2106
|
+
const messages = recordValue(workingLine.messages);
|
|
2107
|
+
delete messages.mode;
|
|
2108
|
+
},
|
|
2109
|
+
);
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
export function saveSelectorBordersComponentPatch(
|
|
2113
|
+
patch: Partial<SelectorBordersComponentConfig>,
|
|
2114
|
+
path = configPath,
|
|
2115
|
+
): PolishedTuiConfig {
|
|
2116
|
+
return saveComponentsMutation(
|
|
2117
|
+
(components) => {
|
|
2118
|
+
const component = components.selectorBorders;
|
|
2119
|
+
if (patch.enabled !== undefined) component.enabled = patch.enabled;
|
|
2120
|
+
if (patch.style !== undefined) component.style = patch.style;
|
|
2121
|
+
if (patch.colorSource !== undefined)
|
|
2122
|
+
component.colorSource = patch.colorSource;
|
|
2123
|
+
},
|
|
2124
|
+
path,
|
|
2125
|
+
undefined,
|
|
2126
|
+
patch.style !== undefined ? "selectorBorders" : undefined,
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
export function saveFooterComponentPatch(
|
|
2131
|
+
patch: Partial<
|
|
2132
|
+
Pick<FooterComponentConfig, "style" | "colorSource" | "modelLabel">
|
|
2133
|
+
>,
|
|
2134
|
+
path = configPath,
|
|
2135
|
+
): PolishedTuiConfig {
|
|
2136
|
+
return saveComponentsMutation(
|
|
2137
|
+
(components) => {
|
|
2138
|
+
const component = components.footer;
|
|
2139
|
+
if (patch.style !== undefined) component.style = patch.style;
|
|
2140
|
+
if (patch.colorSource !== undefined)
|
|
2141
|
+
component.colorSource = patch.colorSource;
|
|
2142
|
+
if (patch.modelLabel !== undefined)
|
|
2143
|
+
component.modelLabel = patch.modelLabel;
|
|
2144
|
+
},
|
|
2145
|
+
path,
|
|
2146
|
+
patch.style !== undefined ? deleteLegacyFooterEnabled : undefined,
|
|
2147
|
+
patch.style !== undefined ? "footer" : undefined,
|
|
2148
|
+
);
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
function applyStarshipStylePatch(
|
|
2152
|
+
style: StarshipFooterStyleConfig,
|
|
2153
|
+
patch: Partial<StarshipFooterStyleConfig>,
|
|
2154
|
+
): void {
|
|
2155
|
+
if (patch.format !== undefined) style.format = patch.format;
|
|
2156
|
+
if (patch.responsive !== undefined) style.responsive = patch.responsive;
|
|
2157
|
+
if (patch.compactFormat !== undefined)
|
|
2158
|
+
style.compactFormat = patch.compactFormat;
|
|
2159
|
+
if (patch.compactMaxLines !== undefined)
|
|
2160
|
+
style.compactMaxLines = patch.compactMaxLines;
|
|
2161
|
+
if (patch.separator !== undefined) style.separator = patch.separator;
|
|
2162
|
+
if (patch.contextStyle !== undefined) style.contextStyle = patch.contextStyle;
|
|
2163
|
+
if (patch.contextThresholds !== undefined) {
|
|
2164
|
+
style.contextThresholds = {
|
|
2165
|
+
...style.contextThresholds,
|
|
2166
|
+
...patch.contextThresholds,
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2169
|
+
if (patch.pathDisplay !== undefined) {
|
|
2170
|
+
style.pathDisplay = { ...style.pathDisplay, ...patch.pathDisplay };
|
|
2171
|
+
}
|
|
2172
|
+
if (patch.segments !== undefined)
|
|
2173
|
+
style.segments = { ...style.segments, ...patch.segments };
|
|
2174
|
+
if (patch.gitBranch !== undefined)
|
|
2175
|
+
style.gitBranch = { ...style.gitBranch, ...patch.gitBranch };
|
|
2176
|
+
if (patch.gitCommit !== undefined)
|
|
2177
|
+
style.gitCommit = { ...style.gitCommit, ...patch.gitCommit };
|
|
2178
|
+
if (patch.gitMetrics !== undefined)
|
|
2179
|
+
style.gitMetrics = { ...style.gitMetrics, ...patch.gitMetrics };
|
|
2180
|
+
if (patch.extensionStatuses !== undefined) {
|
|
2181
|
+
style.extensionStatuses = {
|
|
2182
|
+
...style.extensionStatuses,
|
|
2183
|
+
...patch.extensionStatuses,
|
|
2184
|
+
placements: {
|
|
2185
|
+
...style.extensionStatuses.placements,
|
|
2186
|
+
...patch.extensionStatuses.placements,
|
|
2187
|
+
},
|
|
2188
|
+
colorModes: {
|
|
2189
|
+
...style.extensionStatuses.colorModes,
|
|
2190
|
+
...patch.extensionStatuses.colorModes,
|
|
2191
|
+
},
|
|
2192
|
+
};
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
export function saveStarshipFooterStylePatch(
|
|
2197
|
+
patch: Partial<StarshipFooterStyleConfig>,
|
|
2198
|
+
path = configPath,
|
|
2199
|
+
): PolishedTuiConfig {
|
|
2200
|
+
return saveComponentsMutation(
|
|
2201
|
+
(components) =>
|
|
2202
|
+
applyStarshipStylePatch(components.footer.styles.starship, patch),
|
|
2203
|
+
path,
|
|
2204
|
+
);
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
export function saveColorSourcesPatch(
|
|
2208
|
+
patch: Partial<ColorSourcesConfig>,
|
|
2209
|
+
path = configPath,
|
|
2210
|
+
): PolishedTuiConfig {
|
|
2211
|
+
const valid = validColorSourceEntries(patch);
|
|
2212
|
+
return saveComponentsMutation((components) => {
|
|
2213
|
+
if (valid.starship !== undefined)
|
|
2214
|
+
components.footer.colorSource = valid.starship;
|
|
2215
|
+
if (valid.editor !== undefined) {
|
|
2216
|
+
components.editor.colorSource = valid.editor;
|
|
2217
|
+
components.selectorBorders.colorSource = valid.editor;
|
|
2218
|
+
}
|
|
2219
|
+
if (valid.userMessages !== undefined) {
|
|
2220
|
+
components.userMessages.colorSource = valid.userMessages;
|
|
2221
|
+
}
|
|
2222
|
+
}, path);
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
export function saveUiFeaturesPatch(
|
|
2226
|
+
patch: Partial<UiFeaturesConfig>,
|
|
2227
|
+
path = configPath,
|
|
2228
|
+
): PolishedTuiConfig {
|
|
2229
|
+
const valid = validUiFeatureEntries(patch);
|
|
2230
|
+
return saveComponentsMutation(
|
|
2231
|
+
(components) => {
|
|
2232
|
+
if (valid.editor !== undefined) {
|
|
2233
|
+
components.editor.enabled = valid.editor;
|
|
2234
|
+
components.userMessages.enabled = valid.editor;
|
|
2235
|
+
components.selectorBorders.enabled = valid.editor;
|
|
2236
|
+
}
|
|
2237
|
+
if (valid.statusLine !== undefined) {
|
|
2238
|
+
components.footer.style = valid.statusLine ? "starship" : "native";
|
|
2239
|
+
}
|
|
2240
|
+
if (valid.viewportIndicators !== undefined) {
|
|
2241
|
+
components.editor.viewportIndicators = valid.viewportIndicators;
|
|
2242
|
+
}
|
|
2243
|
+
},
|
|
2244
|
+
path,
|
|
2245
|
+
valid.statusLine !== undefined ? deleteLegacyFooterEnabled : undefined,
|
|
2246
|
+
valid.statusLine !== undefined ? "footer" : undefined,
|
|
2247
|
+
);
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
export function saveFooterSegmentsPatch(
|
|
2251
|
+
patch: Partial<FooterSegmentsConfig>,
|
|
2252
|
+
path = configPath,
|
|
2253
|
+
): PolishedTuiConfig {
|
|
2254
|
+
return saveStarshipFooterStylePatch(
|
|
2255
|
+
{ segments: validFooterSegmentEntries(patch) as FooterSegmentsConfig },
|
|
2256
|
+
path,
|
|
2257
|
+
);
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
export function saveFooterFormatPatch(
|
|
2261
|
+
value: string,
|
|
2262
|
+
path = configPath,
|
|
2263
|
+
): PolishedTuiConfig {
|
|
2264
|
+
return saveStarshipFooterStylePatch(
|
|
2265
|
+
{ format: typeof value === "string" ? value : "" },
|
|
2266
|
+
path,
|
|
2267
|
+
);
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
export function saveResponsiveFooterPatch(
|
|
2271
|
+
patch: Partial<
|
|
2272
|
+
Pick<
|
|
2273
|
+
PolishedTuiConfig,
|
|
2274
|
+
"responsiveFooter" | "compactFooterFormat" | "compactFooterMaxLines"
|
|
2275
|
+
>
|
|
2276
|
+
>,
|
|
2277
|
+
path = configPath,
|
|
2278
|
+
): PolishedTuiConfig {
|
|
2279
|
+
const canonical: Partial<StarshipFooterStyleConfig> = {};
|
|
2280
|
+
if (typeof patch.responsiveFooter === "boolean")
|
|
2281
|
+
canonical.responsive = patch.responsiveFooter;
|
|
2282
|
+
if (typeof patch.compactFooterFormat === "string")
|
|
2283
|
+
canonical.compactFormat = patch.compactFooterFormat;
|
|
2284
|
+
if (patch.compactFooterMaxLines !== undefined) {
|
|
2285
|
+
canonical.compactMaxLines = parseCompactFooterMaxLines(
|
|
2286
|
+
patch.compactFooterMaxLines,
|
|
2287
|
+
);
|
|
2288
|
+
}
|
|
2289
|
+
return saveStarshipFooterStylePatch(canonical, path);
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
export function saveIconsModePatch(
|
|
2293
|
+
mode: IconMode,
|
|
2294
|
+
path = configPath,
|
|
2295
|
+
): PolishedTuiConfig {
|
|
2296
|
+
return mutateConfig(path, (record) => {
|
|
2297
|
+
const existing = isRecord(record.icons) ? { ...record.icons } : {};
|
|
2298
|
+
record.icons = { ...existing, mode: normalizeIconMode(mode) };
|
|
2299
|
+
});
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
export function saveContextStylePatch(
|
|
2303
|
+
style: ContextStyle,
|
|
2304
|
+
path = configPath,
|
|
2305
|
+
): PolishedTuiConfig {
|
|
2306
|
+
return saveStarshipFooterStylePatch(
|
|
2307
|
+
{ contextStyle: parseContextStyle(style) },
|
|
2308
|
+
path,
|
|
2309
|
+
);
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
export function saveSeparatorPatch(
|
|
2313
|
+
separator: SeparatorStyle,
|
|
2314
|
+
path = configPath,
|
|
2315
|
+
): PolishedTuiConfig {
|
|
2316
|
+
return saveStarshipFooterStylePatch(
|
|
2317
|
+
{ separator: parseSeparatorStyle(separator) },
|
|
2318
|
+
path,
|
|
2319
|
+
);
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
export function saveContextThresholdsPatch(
|
|
2323
|
+
thresholds: Partial<ContextThresholds>,
|
|
2324
|
+
path = configPath,
|
|
2325
|
+
): PolishedTuiConfig {
|
|
2326
|
+
if (
|
|
2327
|
+
typeof thresholds.warning === "bigint" ||
|
|
2328
|
+
typeof thresholds.error === "bigint"
|
|
2329
|
+
) {
|
|
2330
|
+
throw new TypeError("Context thresholds must be JSON-serializable numbers");
|
|
2331
|
+
}
|
|
2332
|
+
return saveComponentsMutation((components) => {
|
|
2333
|
+
components.editor.styles.minimalist.contextThresholds = {
|
|
2334
|
+
...components.editor.styles.minimalist.contextThresholds,
|
|
2335
|
+
...thresholds,
|
|
2336
|
+
};
|
|
2337
|
+
components.footer.styles.starship.contextThresholds = {
|
|
2338
|
+
...components.footer.styles.starship.contextThresholds,
|
|
2339
|
+
...thresholds,
|
|
2340
|
+
};
|
|
2341
|
+
}, path);
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
export function savePathDisplayPatch(
|
|
2345
|
+
patch: Partial<PathDisplayConfig>,
|
|
2346
|
+
path = configPath,
|
|
2347
|
+
): PolishedTuiConfig {
|
|
2348
|
+
return saveComponentsMutation((components) => {
|
|
2349
|
+
components.footer.styles.starship.pathDisplay = {
|
|
2350
|
+
...components.footer.styles.starship.pathDisplay,
|
|
2351
|
+
...patch,
|
|
2352
|
+
};
|
|
2353
|
+
}, path);
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
export function saveGitBranchPatch(
|
|
2357
|
+
patch: Partial<GitBranchConfig>,
|
|
2358
|
+
path = configPath,
|
|
2359
|
+
): PolishedTuiConfig {
|
|
2360
|
+
return saveComponentsMutation((components) => {
|
|
2361
|
+
components.footer.styles.starship.gitBranch = {
|
|
2362
|
+
...components.footer.styles.starship.gitBranch,
|
|
2363
|
+
...patch,
|
|
2364
|
+
};
|
|
2365
|
+
}, path);
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
export function saveEditorModelLabel(
|
|
2369
|
+
value: ModelLabelSource,
|
|
2370
|
+
path = configPath,
|
|
2371
|
+
): PolishedTuiConfig {
|
|
2372
|
+
return saveComponentsMutation((components) => {
|
|
2373
|
+
const normalized = parseEditorModelLabel(value);
|
|
2374
|
+
components.editor.modelLabel = normalized;
|
|
2375
|
+
components.footer.modelLabel = normalized;
|
|
2376
|
+
}, path);
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
export function saveEditorStyle(
|
|
2380
|
+
value: EditorStyle,
|
|
2381
|
+
path = configPath,
|
|
2382
|
+
): PolishedTuiConfig {
|
|
2383
|
+
return saveEditorComponentPatch({ style: parseEditorStyle(value) }, path);
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
export function saveMinimalistPatch(
|
|
2387
|
+
patch: Partial<MinimalistConfig>,
|
|
2388
|
+
path = configPath,
|
|
2389
|
+
): PolishedTuiConfig {
|
|
2390
|
+
return saveMinimalistEditorStylePatch(patch, path);
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
export function saveEditorBorderColorMode(
|
|
2394
|
+
value: EditorBorderColorMode,
|
|
2395
|
+
path = configPath,
|
|
2396
|
+
): PolishedTuiConfig {
|
|
2397
|
+
return saveEditorComponentPatch(
|
|
2398
|
+
{ borderColorMode: parseEditorBorderColorMode(value) },
|
|
2399
|
+
path,
|
|
2400
|
+
);
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
export function saveGitCommitPatch(
|
|
2404
|
+
patch: Partial<Pick<GitCommitConfig, "onlyDetached" | "showTag">>,
|
|
2405
|
+
path = configPath,
|
|
2406
|
+
): PolishedTuiConfig {
|
|
2407
|
+
const valid: Partial<GitCommitConfig> = {};
|
|
2408
|
+
if (typeof patch.onlyDetached === "boolean")
|
|
2409
|
+
valid.onlyDetached = patch.onlyDetached;
|
|
2410
|
+
if (typeof patch.showTag === "boolean") valid.showTag = patch.showTag;
|
|
2411
|
+
return saveComponentsMutation((components) => {
|
|
2412
|
+
components.footer.styles.starship.gitCommit = {
|
|
2413
|
+
...components.footer.styles.starship.gitCommit,
|
|
2414
|
+
...valid,
|
|
2415
|
+
};
|
|
2416
|
+
}, path);
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
export function saveGitMetricsPatch(
|
|
2420
|
+
patch: Partial<GitMetricsConfig>,
|
|
2421
|
+
path = configPath,
|
|
2422
|
+
): PolishedTuiConfig {
|
|
2423
|
+
const valid: Partial<GitMetricsConfig> = {};
|
|
2424
|
+
if (typeof patch.onlyNonzero === "boolean")
|
|
2425
|
+
valid.onlyNonzero = patch.onlyNonzero;
|
|
2426
|
+
if (typeof patch.ignoreSubmodules === "boolean")
|
|
2427
|
+
valid.ignoreSubmodules = patch.ignoreSubmodules;
|
|
2428
|
+
return saveComponentsMutation((components) => {
|
|
2429
|
+
components.footer.styles.starship.gitMetrics = {
|
|
2430
|
+
...components.footer.styles.starship.gitMetrics,
|
|
2431
|
+
...valid,
|
|
2432
|
+
};
|
|
2433
|
+
}, path);
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
export function saveExtensionStatusDefaultPlacement(
|
|
2437
|
+
placement: ExtensionStatusPlacement,
|
|
2438
|
+
path = configPath,
|
|
2439
|
+
): PolishedTuiConfig {
|
|
2440
|
+
return saveComponentsMutation((components) => {
|
|
2441
|
+
components.footer.styles.starship.extensionStatuses.defaultPlacement =
|
|
2442
|
+
isExtensionStatusPlacement(placement)
|
|
2443
|
+
? placement
|
|
2444
|
+
: defaultConfig.extensionStatuses.defaultPlacement;
|
|
2445
|
+
}, path);
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
export function saveExtensionStatusPlacement(
|
|
2449
|
+
key: string,
|
|
2450
|
+
placement: ExtensionStatusPlacement,
|
|
2451
|
+
path = configPath,
|
|
2452
|
+
): PolishedTuiConfig {
|
|
2453
|
+
return saveComponentsMutation((components) => {
|
|
2454
|
+
Object.defineProperty(
|
|
2455
|
+
components.footer.styles.starship.extensionStatuses.placements,
|
|
2456
|
+
key,
|
|
2457
|
+
{
|
|
2458
|
+
value: placement,
|
|
2459
|
+
enumerable: true,
|
|
2460
|
+
configurable: true,
|
|
2461
|
+
writable: true,
|
|
2462
|
+
},
|
|
2463
|
+
);
|
|
2464
|
+
}, path);
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
export function saveExtensionStatusColorMode(
|
|
2468
|
+
key: string,
|
|
2469
|
+
colorMode: ExtensionStatusColorMode,
|
|
2470
|
+
path = configPath,
|
|
2471
|
+
): PolishedTuiConfig {
|
|
2472
|
+
return saveComponentsMutation((components) => {
|
|
2473
|
+
Object.defineProperty(
|
|
2474
|
+
components.footer.styles.starship.extensionStatuses.colorModes,
|
|
2475
|
+
key,
|
|
2476
|
+
{
|
|
2477
|
+
value: colorMode,
|
|
2478
|
+
enumerable: true,
|
|
2479
|
+
configurable: true,
|
|
2480
|
+
writable: true,
|
|
2481
|
+
},
|
|
2482
|
+
);
|
|
2483
|
+
}, path);
|
|
2484
|
+
}
|