@rosetears/aili-pi 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -6
- package/THIRD_PARTY_NOTICES.md +14 -3
- package/extensions/header/index.ts +92 -0
- package/extensions/matrix/index.ts +375 -0
- package/extensions/zentui/config.ts +1014 -0
- package/extensions/zentui/extension-status.ts +96 -0
- package/extensions/zentui/fixed-editor/cluster.ts +98 -0
- package/extensions/zentui/fixed-editor/compositor.ts +719 -0
- package/extensions/zentui/fixed-editor/index.ts +223 -0
- package/extensions/zentui/fixed-editor/input.ts +85 -0
- package/extensions/zentui/fixed-editor/pi-compat.ts +296 -0
- package/extensions/zentui/fixed-editor/selection.ts +217 -0
- package/extensions/zentui/fixed-editor/terminal-modes.ts +75 -0
- package/extensions/zentui/fixed-editor/types.ts +37 -0
- package/extensions/zentui/footer-format.ts +279 -0
- package/extensions/zentui/footer.ts +595 -0
- package/extensions/zentui/format.ts +434 -0
- package/extensions/zentui/git.ts +384 -0
- package/extensions/zentui/gradient.ts +70 -0
- package/extensions/zentui/icons.ts +252 -0
- package/extensions/zentui/index.ts +580 -0
- package/extensions/zentui/live-context.ts +75 -0
- package/extensions/zentui/package-version.ts +650 -0
- package/extensions/zentui/project-refresh.ts +104 -0
- package/extensions/zentui/project-state.ts +59 -0
- package/extensions/zentui/prototype-patch-registry.ts +111 -0
- package/extensions/zentui/runtime.ts +841 -0
- package/extensions/zentui/selector-border.ts +77 -0
- package/extensions/zentui/session-lifecycle.ts +60 -0
- package/extensions/zentui/settings-command.ts +897 -0
- package/extensions/zentui/state.ts +55 -0
- package/extensions/zentui/style.ts +332 -0
- package/extensions/zentui/thinking-message.ts +159 -0
- package/extensions/zentui/tool-execution.ts +189 -0
- package/extensions/zentui/ui.ts +618 -0
- package/extensions/zentui/user-message.ts +252 -0
- package/licenses/pi-sakura-cyberdeck-MIT.txt +21 -0
- package/licenses/pi-zentui-MIT.txt +21 -0
- package/manifests/capabilities.json +1 -1
- package/manifests/live-verification.json +14 -8
- package/manifests/provenance.json +18 -5
- package/manifests/sbom.json +19 -3
- package/notices/pi-sakura-cyberdeck-NOTICE.txt +6 -0
- package/package.json +12 -3
- package/scripts/local-package-e2e.ts +1 -1
- package/scripts/sync-global-skills.d.mts +13 -0
- package/scripts/sync-global-skills.mjs +134 -0
- package/src/runtime/credential-guard.ts +58 -0
- package/src/runtime/doctor.ts +1 -1
- package/src/runtime/index.ts +2 -2
- package/src/runtime/path-boundaries.ts +1 -1
- package/src/runtime/registry.ts +6 -2
- package/src/runtime/rem-head.txt +38 -0
- package/src/runtime/rose-context.ts +1 -1
- package/src/runtime/subagents.ts +74 -403
- package/templates/APPEND_SYSTEM.md +10 -8
- package/themes/rem-cyberdeck.json +32 -0
- package/upstream/opencode-global-agents.lock.json +34 -0
|
@@ -0,0 +1,897 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import {
|
|
4
|
+
type AutocompleteItem,
|
|
5
|
+
Key,
|
|
6
|
+
matchesKey,
|
|
7
|
+
type SettingItem,
|
|
8
|
+
SettingsList,
|
|
9
|
+
type SettingsListTheme,
|
|
10
|
+
truncateToWidth,
|
|
11
|
+
} from "@earendil-works/pi-tui";
|
|
12
|
+
import {
|
|
13
|
+
type ColorSource,
|
|
14
|
+
type ColorSourcesConfig,
|
|
15
|
+
type ContextStyle,
|
|
16
|
+
type ExtensionStatusColorMode,
|
|
17
|
+
type ExtensionStatusPlacement,
|
|
18
|
+
type FixedEditorConfig,
|
|
19
|
+
type FooterSegmentsConfig,
|
|
20
|
+
type GitBranchConfig,
|
|
21
|
+
type GitBranchMaxLength,
|
|
22
|
+
getExtensionStatusColorMode,
|
|
23
|
+
getExtensionStatusPlacement,
|
|
24
|
+
type IconMode,
|
|
25
|
+
isExtensionStatusColorMode,
|
|
26
|
+
isExtensionStatusPlacement,
|
|
27
|
+
isSeparatorStyle,
|
|
28
|
+
type PathDisplayConfig,
|
|
29
|
+
type PathDisplayMode,
|
|
30
|
+
type PolishedTuiConfig,
|
|
31
|
+
type SeparatorStyle,
|
|
32
|
+
type UiFeaturesConfig,
|
|
33
|
+
} from "./config.js";
|
|
34
|
+
import { sanitizeExtensionStatusText } from "./extension-status.js";
|
|
35
|
+
import { isIconMode } from "./icons.js";
|
|
36
|
+
import type { SessionLifecycle } from "./session-lifecycle.js";
|
|
37
|
+
import { EDITOR_BORDER_STYLE, renderChromeBorder, safeThemeFg } from "./style.js";
|
|
38
|
+
|
|
39
|
+
const colorSourceValues: ColorSource[] = ["theme", "terminal"];
|
|
40
|
+
const extensionStatusPlacementValues: ExtensionStatusPlacement[] = [
|
|
41
|
+
"off",
|
|
42
|
+
"left",
|
|
43
|
+
"middle",
|
|
44
|
+
"right",
|
|
45
|
+
];
|
|
46
|
+
const extensionStatusColorModeValues: ExtensionStatusColorMode[] = ["zentui", "original"];
|
|
47
|
+
const contextStyleValues: ContextStyle[] = ["text", "gauge", "text+gauge"];
|
|
48
|
+
const separatorStyleValues: SeparatorStyle[] = ["pipe", "dot", "chevron", "none"];
|
|
49
|
+
const pathDisplayModeValues: PathDisplayMode[] = ["basename", "full"];
|
|
50
|
+
const pathDepthValues = ["0", "1", "2", "3", "4", "5"] as const;
|
|
51
|
+
const branchLengthPresetValues = ["full", "10", "20", "30", "40", "50"] as const;
|
|
52
|
+
const iconModeValues: IconMode[] = ["auto", "nerd", "ascii"];
|
|
53
|
+
type FeatureState = "enabled" | "disabled";
|
|
54
|
+
|
|
55
|
+
const featureStateValues: FeatureState[] = ["enabled", "disabled"];
|
|
56
|
+
const settingsSections = [
|
|
57
|
+
"coloring",
|
|
58
|
+
"features",
|
|
59
|
+
"layout",
|
|
60
|
+
"builtinSegments",
|
|
61
|
+
"extensionSegments",
|
|
62
|
+
] as const;
|
|
63
|
+
|
|
64
|
+
type ColorSettingId = "starship" | "editorMessages";
|
|
65
|
+
type FeatureSettingId = keyof UiFeaturesConfig;
|
|
66
|
+
type FooterSegmentSettingId = keyof FooterSegmentsConfig;
|
|
67
|
+
type SettingsSection = (typeof settingsSections)[number];
|
|
68
|
+
type LayoutSettingId =
|
|
69
|
+
| "contextStyle"
|
|
70
|
+
| "separator"
|
|
71
|
+
| "pathDisplay"
|
|
72
|
+
| "pathDepth"
|
|
73
|
+
| "branchLength"
|
|
74
|
+
| "iconMode";
|
|
75
|
+
|
|
76
|
+
type SettingsCommandDeps = {
|
|
77
|
+
sessionLifecycle: SessionLifecycle;
|
|
78
|
+
getConfig: () => PolishedTuiConfig;
|
|
79
|
+
setColorSources: (patch: Partial<ColorSourcesConfig>) => void;
|
|
80
|
+
setUiFeatures: (
|
|
81
|
+
patch: Partial<UiFeaturesConfig>,
|
|
82
|
+
ctx: ExtensionContext,
|
|
83
|
+
) => { applied: boolean; reason?: string };
|
|
84
|
+
setFooterSegments: (patch: Partial<FooterSegmentsConfig>) => void;
|
|
85
|
+
setFooterFormat: (value: string) => void;
|
|
86
|
+
setIconMode: (mode: IconMode) => void;
|
|
87
|
+
setContextStyle: (style: ContextStyle) => void;
|
|
88
|
+
setSeparator: (separator: SeparatorStyle) => void;
|
|
89
|
+
setPathDisplay: (patch: Partial<PathDisplayConfig>) => void;
|
|
90
|
+
setGitBranch: (patch: Partial<GitBranchConfig>) => void;
|
|
91
|
+
getActiveExtensionStatuses: () => ReadonlyMap<string, string>;
|
|
92
|
+
setExtensionStatusPlacement: (key: string, placement: ExtensionStatusPlacement) => void;
|
|
93
|
+
setExtensionStatusColorMode: (key: string, colorMode: ExtensionStatusColorMode) => void;
|
|
94
|
+
setFixedEditor: (patch: Partial<FixedEditorConfig>, ctx: ExtensionContext) => void;
|
|
95
|
+
requestRender: () => void;
|
|
96
|
+
settingsListTheme?: SettingsListTheme;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const colorSettingLabels: Record<ColorSettingId, string> = {
|
|
100
|
+
starship: "Starship/footer colors",
|
|
101
|
+
editorMessages: "Editor + previous messages",
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const colorSettingDescriptions: Record<ColorSettingId, string> = {
|
|
105
|
+
starship:
|
|
106
|
+
"Choose whether footer runtime/git/context colors use Pi theme tokens or terminal palette styles.",
|
|
107
|
+
editorMessages:
|
|
108
|
+
"Choose whether editor and previous user-message borders/rails use Pi theme colors or terminal palette styles.",
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const featureSettingLabels: Record<FeatureSettingId, string> = {
|
|
112
|
+
editor: "Editor",
|
|
113
|
+
statusLine: "Status line",
|
|
114
|
+
copyFriendly: "Copy-friendly mode",
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const featureSettingDescriptions: Record<FeatureSettingId, string> = {
|
|
118
|
+
editor:
|
|
119
|
+
"Enable or disable Zentui's custom editor, selector borders, and previous-message chrome.",
|
|
120
|
+
statusLine: "Enable or disable Zentui's custom footer/status line.",
|
|
121
|
+
copyFriendly:
|
|
122
|
+
"Hide editor and previous-message rail glyphs for cleaner native terminal selection.",
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const footerSegmentSettingLabels: Record<FooterSegmentSettingId, string> = {
|
|
126
|
+
cwd: "Current directory",
|
|
127
|
+
gitBranch: "Git branch",
|
|
128
|
+
gitStatus: "Git status",
|
|
129
|
+
gitCounts: "Git counts",
|
|
130
|
+
sessionDuration: "Session duration",
|
|
131
|
+
username: "Username@host",
|
|
132
|
+
time: "Current time",
|
|
133
|
+
os: "OS icon",
|
|
134
|
+
runtime: "Runtime",
|
|
135
|
+
context: "Context usage",
|
|
136
|
+
tokens: "Token counts",
|
|
137
|
+
cost: "Session cost",
|
|
138
|
+
packageVersion: "Package version",
|
|
139
|
+
gitCommit: "Git commit",
|
|
140
|
+
gitMetrics: "Git line metrics",
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const footerSegmentSettingDescriptions: Record<FooterSegmentSettingId, string> = {
|
|
144
|
+
cwd: "Show or hide the current working directory segment on the left.",
|
|
145
|
+
gitBranch: "Show or hide the git branch name on the left.",
|
|
146
|
+
gitStatus: "Show or hide git status icons and ahead/behind markers.",
|
|
147
|
+
gitCounts:
|
|
148
|
+
"Show numeric ahead/behind and stash counts (requires the Git status segment to be enabled).",
|
|
149
|
+
sessionDuration: "Show session running time on the left, after the runtime.",
|
|
150
|
+
username: "Show user@hostname on the left.",
|
|
151
|
+
time: "Show the current time (HH:MM) on the right.",
|
|
152
|
+
os: "Show an operating-system icon on the left.",
|
|
153
|
+
runtime: "Show or hide the detected runtime/language segment on the left.",
|
|
154
|
+
context: "Show or hide context usage on the right.",
|
|
155
|
+
tokens: "Show or hide input/output token counts on the right.",
|
|
156
|
+
cost: "Show or hide session cost on the right.",
|
|
157
|
+
packageVersion:
|
|
158
|
+
"Show the project’s own manifest version (package.json, Cargo.toml, pyproject.toml, …). Distinct from the runtime segment, which shows the installed toolchain version.",
|
|
159
|
+
gitCommit:
|
|
160
|
+
"Show the current commit hash (and optional exact-match tag). On detached HEAD this provides context the branch segment can’t. Starship `git_commit`-style; default off.",
|
|
161
|
+
gitMetrics:
|
|
162
|
+
"Show aggregate added/deleted line counts (e.g. `+12 −3`) via `git diff HEAD --numstat`. Complements the git status counts. Starship `git_metrics`-style; default off.",
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const directCommandSuggestions = [
|
|
166
|
+
"editor enable",
|
|
167
|
+
"editor disable",
|
|
168
|
+
"editor toggle",
|
|
169
|
+
"statusline enable",
|
|
170
|
+
"statusline disable",
|
|
171
|
+
"statusline toggle",
|
|
172
|
+
"copy-friendly enable",
|
|
173
|
+
"copy-friendly disable",
|
|
174
|
+
"copy-friendly toggle",
|
|
175
|
+
"fixed-editor enable",
|
|
176
|
+
"fixed-editor disable",
|
|
177
|
+
"fixed-editor toggle",
|
|
178
|
+
"format clear",
|
|
179
|
+
"format $cwd on $git_branch $fill $context",
|
|
180
|
+
"format $cwd( on $git_branch)($git_status)$fill($context)( | $cost)",
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
const sectionLabels: Record<SettingsSection, string> = {
|
|
184
|
+
coloring: "Coloring",
|
|
185
|
+
features: "Features",
|
|
186
|
+
layout: "Layout",
|
|
187
|
+
builtinSegments: "Built-in segments",
|
|
188
|
+
extensionSegments: "Extension segments",
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const thirdPartyStatusSettingPrefix = "thirdPartyStatus:";
|
|
192
|
+
const footerSegmentSettingPrefix = "footerSegment:";
|
|
193
|
+
type ThirdPartyStatusSettingKind = "placement" | "colorMode";
|
|
194
|
+
|
|
195
|
+
function isColorSource(value: string): value is ColorSource {
|
|
196
|
+
return value === "theme" || value === "terminal";
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function isColorSettingId(value: string): value is ColorSettingId {
|
|
200
|
+
return value === "starship" || value === "editorMessages";
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function isFeatureSettingId(value: string): value is FeatureSettingId {
|
|
204
|
+
return value === "editor" || value === "statusLine" || value === "copyFriendly";
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function isFooterSegmentSettingId(value: string): value is FooterSegmentSettingId {
|
|
208
|
+
return (
|
|
209
|
+
value === "cwd" ||
|
|
210
|
+
value === "gitBranch" ||
|
|
211
|
+
value === "gitStatus" ||
|
|
212
|
+
value === "gitCounts" ||
|
|
213
|
+
value === "sessionDuration" ||
|
|
214
|
+
value === "runtime" ||
|
|
215
|
+
value === "context" ||
|
|
216
|
+
value === "tokens" ||
|
|
217
|
+
value === "cost" ||
|
|
218
|
+
value === "username" ||
|
|
219
|
+
value === "time" ||
|
|
220
|
+
value === "os" ||
|
|
221
|
+
value === "packageVersion" ||
|
|
222
|
+
value === "gitCommit" ||
|
|
223
|
+
value === "gitMetrics"
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function isFeatureState(value: string): value is FeatureState {
|
|
228
|
+
return value === "enabled" || value === "disabled";
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function isContextStyle(value: string): value is ContextStyle {
|
|
232
|
+
return value === "text" || value === "gauge" || value === "text+gauge";
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function isPathDisplayMode(value: string): value is PathDisplayMode {
|
|
236
|
+
return value === "basename" || value === "full";
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function isPathDepthValue(value: string): boolean {
|
|
240
|
+
return (pathDepthValues as readonly string[]).includes(value);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function parseGitBranchLengthValue(value: string): GitBranchMaxLength | undefined {
|
|
244
|
+
if (value === "full") return value;
|
|
245
|
+
const parsed = Number(value);
|
|
246
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function branchLengthValues(maxLength: GitBranchMaxLength): string[] {
|
|
250
|
+
const current = String(maxLength);
|
|
251
|
+
return (branchLengthPresetValues as readonly string[]).includes(current)
|
|
252
|
+
? [...branchLengthPresetValues]
|
|
253
|
+
: [current, ...branchLengthPresetValues];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function isLayoutSettingId(value: string): value is LayoutSettingId {
|
|
257
|
+
return (
|
|
258
|
+
value === "contextStyle" ||
|
|
259
|
+
value === "separator" ||
|
|
260
|
+
value === "pathDisplay" ||
|
|
261
|
+
value === "pathDepth" ||
|
|
262
|
+
value === "branchLength" ||
|
|
263
|
+
value === "iconMode"
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function editorMessageValue(config: PolishedTuiConfig): ColorSource | "mixed" {
|
|
268
|
+
return config.colorSources.editor === config.colorSources.userMessages
|
|
269
|
+
? config.colorSources.editor
|
|
270
|
+
: "mixed";
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function patchForSetting(id: ColorSettingId, value: ColorSource): Partial<ColorSourcesConfig> {
|
|
274
|
+
return id === "starship" ? { starship: value } : { editor: value, userMessages: value };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function featureValue(enabled: boolean): FeatureState {
|
|
278
|
+
return enabled ? "enabled" : "disabled";
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function featurePatch(id: FeatureSettingId, value: FeatureState): Partial<UiFeaturesConfig> {
|
|
282
|
+
return { [id]: value === "enabled" } as Partial<UiFeaturesConfig>;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function footerSegmentSettingId(key: FooterSegmentSettingId): string {
|
|
286
|
+
return `${footerSegmentSettingPrefix}${key}`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function footerSegmentSettingFromId(id: string): FooterSegmentSettingId | undefined {
|
|
290
|
+
if (!id.startsWith(footerSegmentSettingPrefix)) return undefined;
|
|
291
|
+
const key = id.slice(footerSegmentSettingPrefix.length);
|
|
292
|
+
return isFooterSegmentSettingId(key) ? key : undefined;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function footerSegmentPatch(
|
|
296
|
+
id: FooterSegmentSettingId,
|
|
297
|
+
value: FeatureState,
|
|
298
|
+
): Partial<FooterSegmentsConfig> {
|
|
299
|
+
return { [id]: value === "enabled" } as Partial<FooterSegmentsConfig>;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function usageText(): string {
|
|
303
|
+
return 'Usage: /zentui [editor|statusline|copy-friendly] [enable|disable|toggle] or /zentui format "<template>"';
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function featureNotification(
|
|
307
|
+
feature: FeatureSettingId,
|
|
308
|
+
value: FeatureState,
|
|
309
|
+
result: { applied: boolean; reason?: string },
|
|
310
|
+
): string {
|
|
311
|
+
const base = `${featureSettingLabels[feature]}: ${value}`;
|
|
312
|
+
return result.applied ? base : `${base} (${result.reason ?? "reload Pi to apply this change"})`;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function parseDirectFeatureCommand(
|
|
316
|
+
args: string,
|
|
317
|
+
config: PolishedTuiConfig,
|
|
318
|
+
): { feature: FeatureSettingId; enabled: boolean } | undefined {
|
|
319
|
+
const normalized = args.trim().toLowerCase().replaceAll(/[_-]+/g, " ");
|
|
320
|
+
if (!normalized) return undefined;
|
|
321
|
+
|
|
322
|
+
const words = normalized.split(/\s+/g).filter(Boolean);
|
|
323
|
+
const hasWord = (value: string) => words.includes(value);
|
|
324
|
+
const feature = hasWord("editor")
|
|
325
|
+
? "editor"
|
|
326
|
+
: hasWord("footer") || hasWord("statusline") || hasWord("status")
|
|
327
|
+
? "statusLine"
|
|
328
|
+
: hasWord("copyfriendly") || hasWord("copy")
|
|
329
|
+
? "copyFriendly"
|
|
330
|
+
: undefined;
|
|
331
|
+
const action = hasWord("toggle")
|
|
332
|
+
? "toggle"
|
|
333
|
+
: hasWord("enable") || hasWord("enabled") || hasWord("on")
|
|
334
|
+
? "enable"
|
|
335
|
+
: hasWord("disable") || hasWord("disabled") || hasWord("off")
|
|
336
|
+
? "disable"
|
|
337
|
+
: undefined;
|
|
338
|
+
|
|
339
|
+
if (!feature || !action) return undefined;
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
feature,
|
|
343
|
+
enabled: action === "toggle" ? !config.features[feature] : action === "enable",
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function parseFixedEditorCommand(
|
|
348
|
+
args: string,
|
|
349
|
+
config: PolishedTuiConfig,
|
|
350
|
+
): { enabled: boolean } | undefined {
|
|
351
|
+
const normalized = args.trim().toLowerCase().replaceAll(/[_-]+/g, " ");
|
|
352
|
+
if (!normalized) return undefined;
|
|
353
|
+
|
|
354
|
+
const words = normalized.split(/\s+/g).filter(Boolean);
|
|
355
|
+
const hasWord = (value: string) => words.includes(value);
|
|
356
|
+
if (!hasWord("fixededitor") && !(hasWord("fixed") && hasWord("editor"))) return undefined;
|
|
357
|
+
|
|
358
|
+
const action = hasWord("toggle")
|
|
359
|
+
? "toggle"
|
|
360
|
+
: hasWord("enable") || hasWord("enabled") || hasWord("on")
|
|
361
|
+
? "enable"
|
|
362
|
+
: hasWord("disable") || hasWord("disabled") || hasWord("off")
|
|
363
|
+
? "disable"
|
|
364
|
+
: undefined;
|
|
365
|
+
if (!action) return undefined;
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
enabled: action === "toggle" ? !config.fixedEditor.enabled : action === "enable",
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function parseFormatCommand(args: string): { value: string | undefined } | undefined {
|
|
373
|
+
const trimmed = args.trim();
|
|
374
|
+
if (!trimmed.toLowerCase().startsWith("format")) return undefined;
|
|
375
|
+
|
|
376
|
+
const rest = trimmed.slice("format".length).trim();
|
|
377
|
+
if (!rest || rest.toLowerCase() === "clear") return { value: undefined };
|
|
378
|
+
|
|
379
|
+
const unquoted =
|
|
380
|
+
rest.startsWith('"') && rest.endsWith('"') && rest.length >= 2 ? rest.slice(1, -1) : rest;
|
|
381
|
+
return { value: unquoted };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function argumentCompletions(prefix: string): AutocompleteItem[] | null {
|
|
385
|
+
const trimmedPrefix = prefix.trimStart().toLowerCase();
|
|
386
|
+
const items = directCommandSuggestions.map((value) => ({ value, label: value }));
|
|
387
|
+
const matches = items.filter((item) => item.value.startsWith(trimmedPrefix));
|
|
388
|
+
return matches.length > 0 ? matches : null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function thirdPartyStatusSettingId(key: string, kind: ThirdPartyStatusSettingKind): string {
|
|
392
|
+
return `${thirdPartyStatusSettingPrefix}${kind}:${key}`;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function thirdPartyStatusSettingFromId(
|
|
396
|
+
id: string,
|
|
397
|
+
): { kind: ThirdPartyStatusSettingKind; key: string } | undefined {
|
|
398
|
+
if (!id.startsWith(thirdPartyStatusSettingPrefix)) return undefined;
|
|
399
|
+
const rest = id.slice(thirdPartyStatusSettingPrefix.length);
|
|
400
|
+
const separatorIndex = rest.indexOf(":");
|
|
401
|
+
if (separatorIndex < 0) return undefined;
|
|
402
|
+
|
|
403
|
+
const kind = rest.slice(0, separatorIndex);
|
|
404
|
+
if (kind !== "placement" && kind !== "colorMode") return undefined;
|
|
405
|
+
|
|
406
|
+
return { kind, key: rest.slice(separatorIndex + 1) };
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function buildItems(
|
|
410
|
+
section: SettingsSection,
|
|
411
|
+
config: PolishedTuiConfig,
|
|
412
|
+
activeStatuses: ReadonlyMap<string, string>,
|
|
413
|
+
): SettingItem[] {
|
|
414
|
+
if (section === "coloring") {
|
|
415
|
+
return (Object.keys(colorSettingLabels) as ColorSettingId[]).map((key) => ({
|
|
416
|
+
id: key,
|
|
417
|
+
label: colorSettingLabels[key],
|
|
418
|
+
description: colorSettingDescriptions[key],
|
|
419
|
+
currentValue: key === "starship" ? config.colorSources.starship : editorMessageValue(config),
|
|
420
|
+
values: colorSourceValues,
|
|
421
|
+
}));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (section === "features") {
|
|
425
|
+
const items: SettingItem[] = (Object.keys(featureSettingLabels) as FeatureSettingId[]).map(
|
|
426
|
+
(key) => ({
|
|
427
|
+
id: key,
|
|
428
|
+
label: featureSettingLabels[key],
|
|
429
|
+
description: featureSettingDescriptions[key],
|
|
430
|
+
currentValue: featureValue(config.features[key]),
|
|
431
|
+
values: featureStateValues,
|
|
432
|
+
}),
|
|
433
|
+
);
|
|
434
|
+
items.push({
|
|
435
|
+
id: "fixedEditor",
|
|
436
|
+
label: "Fixed editor (experimental)",
|
|
437
|
+
description:
|
|
438
|
+
"Pin editor + footer at bottom while transcript scrolls. Uses alternate screen mode.",
|
|
439
|
+
currentValue: featureValue(config.fixedEditor.enabled),
|
|
440
|
+
values: featureStateValues,
|
|
441
|
+
});
|
|
442
|
+
if (config.fixedEditor.enabled) {
|
|
443
|
+
items.push({
|
|
444
|
+
id: "fixedEditorMouseScroll",
|
|
445
|
+
label: "Mouse scroll",
|
|
446
|
+
description:
|
|
447
|
+
"Scroll transcript with mouse wheel. Breaks native terminal selection and tmux scrollback.",
|
|
448
|
+
currentValue: featureValue(config.fixedEditor.mouseScroll),
|
|
449
|
+
values: featureStateValues,
|
|
450
|
+
});
|
|
451
|
+
items.push({
|
|
452
|
+
id: "fixedEditorCopyNotice",
|
|
453
|
+
label: "Copy notice",
|
|
454
|
+
description: "Show a 'Copied to clipboard' message when drag-selecting text.",
|
|
455
|
+
currentValue: featureValue(config.fixedEditor.copyNotice),
|
|
456
|
+
values: featureStateValues,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
return items;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (section === "layout") {
|
|
463
|
+
return [
|
|
464
|
+
{
|
|
465
|
+
id: "contextStyle",
|
|
466
|
+
label: "Context style",
|
|
467
|
+
description: "Render context as text, a gauge bar, or both.",
|
|
468
|
+
currentValue: config.contextStyle,
|
|
469
|
+
values: contextStyleValues,
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
id: "separator",
|
|
473
|
+
label: "Separator",
|
|
474
|
+
description: "Choose the separator between default footer segments.",
|
|
475
|
+
currentValue: config.separator,
|
|
476
|
+
values: separatorStyleValues,
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
id: "pathDisplay",
|
|
480
|
+
label: "Path display",
|
|
481
|
+
description: "Show cwd as basename or full path (home contracted to ~).",
|
|
482
|
+
currentValue: config.pathDisplay.mode,
|
|
483
|
+
values: pathDisplayModeValues,
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
id: "pathDepth",
|
|
487
|
+
label: "Path depth",
|
|
488
|
+
description:
|
|
489
|
+
"In full mode, trailing directories to show (0 = all, max 5). Ignored for basename.",
|
|
490
|
+
currentValue: String(config.pathDisplay.depth),
|
|
491
|
+
values: [...pathDepthValues],
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
id: "branchLength",
|
|
495
|
+
label: "Branch length",
|
|
496
|
+
description: "Show the full branch name or truncate it to a preset visible width.",
|
|
497
|
+
currentValue: String(config.gitBranch.maxLength),
|
|
498
|
+
values: branchLengthValues(config.gitBranch.maxLength),
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
id: "iconMode",
|
|
502
|
+
label: "Icon mode",
|
|
503
|
+
description: "auto/nerd use Nerd Font glyphs; ascii uses plain fallbacks.",
|
|
504
|
+
currentValue: config.icons.mode,
|
|
505
|
+
values: iconModeValues,
|
|
506
|
+
},
|
|
507
|
+
];
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (section === "builtinSegments") {
|
|
511
|
+
return (Object.keys(footerSegmentSettingLabels) as FooterSegmentSettingId[]).map((key) => ({
|
|
512
|
+
id: footerSegmentSettingId(key),
|
|
513
|
+
label: footerSegmentSettingLabels[key],
|
|
514
|
+
description: footerSegmentSettingDescriptions[key],
|
|
515
|
+
currentValue: featureValue(config.footerSegments[key]),
|
|
516
|
+
values: featureStateValues,
|
|
517
|
+
}));
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const statuses = Array.from(activeStatuses.entries()).sort(([a], [b]) =>
|
|
521
|
+
a < b ? -1 : a > b ? 1 : 0,
|
|
522
|
+
);
|
|
523
|
+
if (statuses.length === 0) {
|
|
524
|
+
return [
|
|
525
|
+
{
|
|
526
|
+
id: "noThirdPartyStatuses",
|
|
527
|
+
label: "No active statuses",
|
|
528
|
+
description: "This tab only lists statuses currently published through ctx.ui.setStatus().",
|
|
529
|
+
currentValue: "—",
|
|
530
|
+
},
|
|
531
|
+
];
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return statuses.flatMap(([key, value]) => {
|
|
535
|
+
const sanitizedText = sanitizeExtensionStatusText(value);
|
|
536
|
+
const description = sanitizedText ? `Current status: ${sanitizedText}` : undefined;
|
|
537
|
+
return [
|
|
538
|
+
{
|
|
539
|
+
id: thirdPartyStatusSettingId(key, "placement"),
|
|
540
|
+
label: `${key} placement`,
|
|
541
|
+
description,
|
|
542
|
+
currentValue: getExtensionStatusPlacement(config, key),
|
|
543
|
+
values: extensionStatusPlacementValues,
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
id: thirdPartyStatusSettingId(key, "colorMode"),
|
|
547
|
+
label: `${key} color`,
|
|
548
|
+
description,
|
|
549
|
+
currentValue: getExtensionStatusColorMode(config, key),
|
|
550
|
+
values: extensionStatusColorModeValues,
|
|
551
|
+
},
|
|
552
|
+
];
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function nextSection(section: SettingsSection): SettingsSection {
|
|
557
|
+
const currentIndex = settingsSections.indexOf(section);
|
|
558
|
+
return settingsSections[(currentIndex + 1) % settingsSections.length] ?? "coloring";
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function previousSection(section: SettingsSection): SettingsSection {
|
|
562
|
+
const currentIndex = settingsSections.indexOf(section);
|
|
563
|
+
return (
|
|
564
|
+
settingsSections[(currentIndex - 1 + settingsSections.length) % settingsSections.length] ??
|
|
565
|
+
"coloring"
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function formatSectionTabs(
|
|
570
|
+
activeSection: SettingsSection,
|
|
571
|
+
theme: ExtensionContext["ui"]["theme"],
|
|
572
|
+
): string {
|
|
573
|
+
const rendered = settingsSections.map((section) => {
|
|
574
|
+
const label = sectionLabels[section];
|
|
575
|
+
return section === activeSection ? theme.bold(label) : safeThemeFg(theme, "muted", label);
|
|
576
|
+
});
|
|
577
|
+
return ` ${rendered.join(safeThemeFg(theme, "muted", " / "))}`;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function withSectionFooter(lines: string[], theme: ExtensionContext["ui"]["theme"]): string[] {
|
|
581
|
+
const next = [...lines];
|
|
582
|
+
for (let index = next.length - 1; index >= 0; index -= 1) {
|
|
583
|
+
if (next[index]?.includes("Enter/Space")) {
|
|
584
|
+
next[index] = safeThemeFg(
|
|
585
|
+
theme,
|
|
586
|
+
"muted",
|
|
587
|
+
" Enter/Space to change · Tab/Shift+Tab to switch sections · Esc to close",
|
|
588
|
+
);
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return next;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export function registerZentuiSettingsCommand(pi: ExtensionAPI, deps: SettingsCommandDeps): void {
|
|
596
|
+
pi.registerCommand("zentui", {
|
|
597
|
+
description: "Configure Zentui",
|
|
598
|
+
getArgumentCompletions: argumentCompletions,
|
|
599
|
+
handler: async (_args, ctx) => {
|
|
600
|
+
const args = typeof _args === "string" ? _args : "";
|
|
601
|
+
|
|
602
|
+
const formatCommand = parseFormatCommand(args);
|
|
603
|
+
if (formatCommand) {
|
|
604
|
+
try {
|
|
605
|
+
deps.setFooterFormat(formatCommand.value ?? "");
|
|
606
|
+
deps.requestRender();
|
|
607
|
+
if (ctx.hasUI) {
|
|
608
|
+
if (formatCommand.value === undefined) {
|
|
609
|
+
ctx.ui.notify("Footer format cleared (using default layout)", "info");
|
|
610
|
+
} else {
|
|
611
|
+
ctx.ui.notify(`Footer format: ${formatCommand.value}`, "info");
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
} catch (error) {
|
|
615
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
616
|
+
if (ctx.hasUI) ctx.ui.notify(`Could not update footer format: ${message}`, "error");
|
|
617
|
+
}
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const directCommand = parseDirectFeatureCommand(args, deps.getConfig());
|
|
622
|
+
if (directCommand) {
|
|
623
|
+
try {
|
|
624
|
+
const result = deps.setUiFeatures(
|
|
625
|
+
{ [directCommand.feature]: directCommand.enabled },
|
|
626
|
+
ctx,
|
|
627
|
+
);
|
|
628
|
+
deps.requestRender();
|
|
629
|
+
if (ctx.hasUI) {
|
|
630
|
+
ctx.ui.notify(
|
|
631
|
+
featureNotification(
|
|
632
|
+
directCommand.feature,
|
|
633
|
+
featureValue(directCommand.enabled),
|
|
634
|
+
result,
|
|
635
|
+
),
|
|
636
|
+
"info",
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
} catch (error) {
|
|
640
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
641
|
+
if (ctx.hasUI) ctx.ui.notify(`Could not update Zentui settings: ${message}`, "error");
|
|
642
|
+
}
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const fixedEditorCommand = parseFixedEditorCommand(args, deps.getConfig());
|
|
647
|
+
if (fixedEditorCommand) {
|
|
648
|
+
try {
|
|
649
|
+
deps.setFixedEditor({ enabled: fixedEditorCommand.enabled }, ctx);
|
|
650
|
+
deps.requestRender();
|
|
651
|
+
if (ctx.hasUI) {
|
|
652
|
+
ctx.ui.notify(`Fixed editor: ${featureValue(fixedEditorCommand.enabled)}`, "info");
|
|
653
|
+
}
|
|
654
|
+
} catch (error) {
|
|
655
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
656
|
+
if (ctx.hasUI) ctx.ui.notify(`Could not update fixed editor: ${message}`, "error");
|
|
657
|
+
}
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (args.trim()) {
|
|
662
|
+
if (ctx.hasUI) ctx.ui.notify(usageText(), "warning");
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const mode = (ctx as typeof ctx & { mode?: string }).mode;
|
|
667
|
+
if (!ctx.hasUI || (mode !== undefined && mode !== "tui")) return;
|
|
668
|
+
|
|
669
|
+
await ctx.ui.custom<void>((tui, theme, _keybindings, done) => {
|
|
670
|
+
const settingsListTheme = deps.settingsListTheme ?? getSettingsListTheme();
|
|
671
|
+
let activeSection: SettingsSection = "coloring";
|
|
672
|
+
const applyFeatureChange = (id: FeatureSettingId, newValue: FeatureState) => {
|
|
673
|
+
const result = deps.setUiFeatures(featurePatch(id, newValue), ctx);
|
|
674
|
+
deps.requestRender();
|
|
675
|
+
ctx.ui.notify(featureNotification(id, newValue, result), "info");
|
|
676
|
+
tui.requestRender();
|
|
677
|
+
};
|
|
678
|
+
let settingsList: SettingsList;
|
|
679
|
+
const makeSettingsList = () =>
|
|
680
|
+
new SettingsList(
|
|
681
|
+
buildItems(activeSection, deps.getConfig(), deps.getActiveExtensionStatuses()),
|
|
682
|
+
8,
|
|
683
|
+
settingsListTheme,
|
|
684
|
+
(id, newValue) => {
|
|
685
|
+
try {
|
|
686
|
+
if (isColorSettingId(id) && isColorSource(newValue)) {
|
|
687
|
+
deps.setColorSources(patchForSetting(id, newValue));
|
|
688
|
+
settingsList.updateValue(id, newValue);
|
|
689
|
+
deps.requestRender();
|
|
690
|
+
ctx.ui.notify(`${colorSettingLabels[id]}: ${newValue}`, "info");
|
|
691
|
+
tui.requestRender();
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (isFeatureSettingId(id) && isFeatureState(newValue)) {
|
|
696
|
+
if (id === "editor") {
|
|
697
|
+
done(undefined);
|
|
698
|
+
// Changing the editor component while ctx.ui.custom() is active clears the
|
|
699
|
+
// custom component without resolving it, leaving Pi's input loop stuck.
|
|
700
|
+
// Close the settings UI first, then apply the editor swap on the next tick.
|
|
701
|
+
const applyEditorChange = () => {
|
|
702
|
+
try {
|
|
703
|
+
applyFeatureChange(id, newValue);
|
|
704
|
+
} catch (error) {
|
|
705
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
706
|
+
ctx.ui.notify(`Could not update Zentui settings: ${message}`, "error");
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
deps.sessionLifecycle.defer(applyEditorChange);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
applyFeatureChange(id, newValue);
|
|
714
|
+
settingsList.updateValue(id, newValue);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
if (isLayoutSettingId(id)) {
|
|
719
|
+
if (id === "contextStyle" && isContextStyle(newValue)) {
|
|
720
|
+
deps.setContextStyle(newValue);
|
|
721
|
+
settingsList.updateValue(id, newValue);
|
|
722
|
+
deps.requestRender();
|
|
723
|
+
ctx.ui.notify(`Context style: ${newValue}`, "info");
|
|
724
|
+
tui.requestRender();
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
if (id === "separator" && isSeparatorStyle(newValue)) {
|
|
729
|
+
deps.setSeparator(newValue);
|
|
730
|
+
settingsList.updateValue(id, newValue);
|
|
731
|
+
deps.requestRender();
|
|
732
|
+
ctx.ui.notify(`Separator: ${newValue}`, "info");
|
|
733
|
+
tui.requestRender();
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (id === "pathDisplay" && isPathDisplayMode(newValue)) {
|
|
738
|
+
deps.setPathDisplay({ mode: newValue });
|
|
739
|
+
settingsList.updateValue(id, newValue);
|
|
740
|
+
deps.requestRender();
|
|
741
|
+
ctx.ui.notify(`Path display: ${newValue}`, "info");
|
|
742
|
+
tui.requestRender();
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (id === "pathDepth" && isPathDepthValue(newValue)) {
|
|
747
|
+
deps.setPathDisplay({ depth: Number(newValue) });
|
|
748
|
+
settingsList.updateValue(id, newValue);
|
|
749
|
+
deps.requestRender();
|
|
750
|
+
ctx.ui.notify(`Path depth: ${newValue}`, "info");
|
|
751
|
+
tui.requestRender();
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (id === "branchLength") {
|
|
756
|
+
const maxLength = parseGitBranchLengthValue(newValue);
|
|
757
|
+
if (maxLength === undefined) return;
|
|
758
|
+
deps.setGitBranch({ maxLength });
|
|
759
|
+
settingsList.updateValue(id, newValue);
|
|
760
|
+
deps.requestRender();
|
|
761
|
+
ctx.ui.notify(`Branch length: ${newValue}`, "info");
|
|
762
|
+
tui.requestRender();
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (id === "iconMode" && isIconMode(newValue)) {
|
|
767
|
+
deps.setIconMode(newValue);
|
|
768
|
+
settingsList.updateValue(id, newValue);
|
|
769
|
+
deps.requestRender();
|
|
770
|
+
ctx.ui.notify(`Icon mode: ${newValue}`, "info");
|
|
771
|
+
tui.requestRender();
|
|
772
|
+
}
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
const footerSegmentSetting = footerSegmentSettingFromId(id);
|
|
777
|
+
if (footerSegmentSetting && isFeatureState(newValue)) {
|
|
778
|
+
deps.setFooterSegments(footerSegmentPatch(footerSegmentSetting, newValue));
|
|
779
|
+
settingsList.updateValue(id, newValue);
|
|
780
|
+
deps.requestRender();
|
|
781
|
+
ctx.ui.notify(
|
|
782
|
+
`${footerSegmentSettingLabels[footerSegmentSetting]}: ${newValue}`,
|
|
783
|
+
"info",
|
|
784
|
+
);
|
|
785
|
+
tui.requestRender();
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (id === "fixedEditor" && isFeatureState(newValue)) {
|
|
790
|
+
deps.setFixedEditor({ enabled: newValue === "enabled" }, ctx);
|
|
791
|
+
settingsList = makeSettingsList();
|
|
792
|
+
deps.requestRender();
|
|
793
|
+
ctx.ui.notify(`Fixed editor: ${newValue}`, "info");
|
|
794
|
+
tui.requestRender();
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
if (id === "fixedEditorMouseScroll" && isFeatureState(newValue)) {
|
|
798
|
+
deps.setFixedEditor({ mouseScroll: newValue === "enabled" }, ctx);
|
|
799
|
+
settingsList.updateValue(id, newValue);
|
|
800
|
+
deps.requestRender();
|
|
801
|
+
ctx.ui.notify(`Mouse scroll: ${newValue}`, "info");
|
|
802
|
+
tui.requestRender();
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
if (id === "fixedEditorCopyNotice" && isFeatureState(newValue)) {
|
|
806
|
+
deps.setFixedEditor({ copyNotice: newValue === "enabled" }, ctx);
|
|
807
|
+
settingsList.updateValue(id, newValue);
|
|
808
|
+
deps.requestRender();
|
|
809
|
+
ctx.ui.notify(`Copy notice: ${newValue}`, "info");
|
|
810
|
+
tui.requestRender();
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const thirdPartyStatusSetting = thirdPartyStatusSettingFromId(id);
|
|
815
|
+
if (
|
|
816
|
+
thirdPartyStatusSetting?.kind === "placement" &&
|
|
817
|
+
isExtensionStatusPlacement(newValue)
|
|
818
|
+
) {
|
|
819
|
+
deps.setExtensionStatusPlacement(thirdPartyStatusSetting.key, newValue);
|
|
820
|
+
settingsList.updateValue(id, newValue);
|
|
821
|
+
deps.requestRender();
|
|
822
|
+
ctx.ui.notify(
|
|
823
|
+
`Third-party status ${thirdPartyStatusSetting.key} placement: ${newValue}`,
|
|
824
|
+
"info",
|
|
825
|
+
);
|
|
826
|
+
tui.requestRender();
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
if (
|
|
831
|
+
thirdPartyStatusSetting?.kind === "colorMode" &&
|
|
832
|
+
isExtensionStatusColorMode(newValue)
|
|
833
|
+
) {
|
|
834
|
+
deps.setExtensionStatusColorMode(thirdPartyStatusSetting.key, newValue);
|
|
835
|
+
settingsList.updateValue(id, newValue);
|
|
836
|
+
deps.requestRender();
|
|
837
|
+
ctx.ui.notify(
|
|
838
|
+
`Third-party status ${thirdPartyStatusSetting.key} color: ${newValue}`,
|
|
839
|
+
"info",
|
|
840
|
+
);
|
|
841
|
+
tui.requestRender();
|
|
842
|
+
}
|
|
843
|
+
} catch (error) {
|
|
844
|
+
settingsList = makeSettingsList();
|
|
845
|
+
tui.requestRender();
|
|
846
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
847
|
+
ctx.ui.notify(`Could not update Zentui settings: ${message}`, "error");
|
|
848
|
+
}
|
|
849
|
+
},
|
|
850
|
+
() => done(undefined),
|
|
851
|
+
);
|
|
852
|
+
settingsList = makeSettingsList();
|
|
853
|
+
const switchSection = (direction: "forward" | "backward") => {
|
|
854
|
+
activeSection =
|
|
855
|
+
direction === "forward" ? nextSection(activeSection) : previousSection(activeSection);
|
|
856
|
+
settingsList = makeSettingsList();
|
|
857
|
+
tui.requestRender();
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
return {
|
|
861
|
+
render(width: number) {
|
|
862
|
+
const colorSource = deps.getConfig().colorSources.editor;
|
|
863
|
+
const border = renderChromeBorder(
|
|
864
|
+
theme,
|
|
865
|
+
colorSource,
|
|
866
|
+
EDITOR_BORDER_STYLE,
|
|
867
|
+
"─".repeat(Math.max(0, width)),
|
|
868
|
+
);
|
|
869
|
+
return [
|
|
870
|
+
truncateToWidth(border, width, ""),
|
|
871
|
+
truncateToWidth(formatSectionTabs(activeSection, theme), width, ""),
|
|
872
|
+
truncateToWidth(border, width, ""),
|
|
873
|
+
...withSectionFooter(settingsList.render(width), theme).map((line) =>
|
|
874
|
+
truncateToWidth(line, width, ""),
|
|
875
|
+
),
|
|
876
|
+
truncateToWidth(border, width, ""),
|
|
877
|
+
];
|
|
878
|
+
},
|
|
879
|
+
invalidate() {
|
|
880
|
+
settingsList.invalidate();
|
|
881
|
+
},
|
|
882
|
+
handleInput(data: string) {
|
|
883
|
+
if (matchesKey(data, Key.tab)) {
|
|
884
|
+
switchSection("forward");
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
if (matchesKey(data, Key.shift("tab"))) {
|
|
888
|
+
switchSection("backward");
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
settingsList.handleInput(data);
|
|
892
|
+
},
|
|
893
|
+
};
|
|
894
|
+
});
|
|
895
|
+
},
|
|
896
|
+
});
|
|
897
|
+
}
|