@eiei114/pi-sub-bar 1.5.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/CHANGELOG.md +201 -0
- package/README.md +200 -0
- package/index.ts +1103 -0
- package/package.json +39 -0
- package/src/core-settings.ts +25 -0
- package/src/dividers.ts +48 -0
- package/src/errors.ts +71 -0
- package/src/formatting.ts +937 -0
- package/src/paths.ts +21 -0
- package/src/providers/extras.ts +21 -0
- package/src/providers/metadata.ts +199 -0
- package/src/providers/settings.ts +359 -0
- package/src/providers/windows.ts +23 -0
- package/src/settings/display.ts +786 -0
- package/src/settings/menu.ts +183 -0
- package/src/settings/themes.ts +378 -0
- package/src/settings/ui.ts +1388 -0
- package/src/settings-types.ts +651 -0
- package/src/settings-ui.ts +5 -0
- package/src/settings.ts +176 -0
- package/src/share.ts +75 -0
- package/src/status.ts +103 -0
- package/src/storage.ts +61 -0
- package/src/types.ts +25 -0
- package/src/ui/keybindings.ts +92 -0
- package/src/ui/settings-list.ts +304 -0
- package/src/usage/types.ts +5 -0
- package/src/utils.ts +42 -0
- package/test/all.test.ts +6 -0
- package/test/dividers.test.ts +34 -0
- package/test/formatting.test.ts +437 -0
- package/test/keybindings.test.ts +59 -0
- package/test/providers.test.ts +42 -0
- package/test/settings.test.ts +336 -0
- package/test/status.test.ts +27 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,1388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings UI for sub-bar
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
6
|
+
import { DynamicBorder, getSettingsListTheme } from "@mariozechner/pi-coding-agent";
|
|
7
|
+
import { Container, Input, SelectList, Spacer, Text } from "@mariozechner/pi-tui";
|
|
8
|
+
import { SettingsList, type SettingItem, CUSTOM_OPTION } from "../ui/settings-list.js";
|
|
9
|
+
import type { ProviderName } from "../types.js";
|
|
10
|
+
import type { Settings } from "../settings-types.js";
|
|
11
|
+
import type { CoreSettings } from "@eiei114/pi-sub-shared";
|
|
12
|
+
import { getFallbackCoreSettings } from "../core-settings.js";
|
|
13
|
+
import { getDefaultSettings } from "../settings-types.js";
|
|
14
|
+
import { getSettings, saveSettings } from "../settings.js";
|
|
15
|
+
import { PROVIDER_DISPLAY_NAMES } from "../providers/metadata.js";
|
|
16
|
+
import { buildProviderSettingsItems, applyProviderSettingsChange } from "../providers/settings.js";
|
|
17
|
+
import {
|
|
18
|
+
buildDisplayLayoutItems,
|
|
19
|
+
buildDisplayResetItems,
|
|
20
|
+
buildDisplayColorItems,
|
|
21
|
+
buildDisplayBarItems,
|
|
22
|
+
buildDisplayProviderItems,
|
|
23
|
+
buildDisplayStatusItems,
|
|
24
|
+
buildDisplayDividerItems,
|
|
25
|
+
buildUsageColorTargetItems,
|
|
26
|
+
formatUsageColorTargetsSummary,
|
|
27
|
+
applyDisplayChange,
|
|
28
|
+
} from "./display.js";
|
|
29
|
+
import {
|
|
30
|
+
buildMainMenuItems,
|
|
31
|
+
buildProviderListItems,
|
|
32
|
+
buildDisplayMenuItems,
|
|
33
|
+
buildDisplayThemeMenuItems,
|
|
34
|
+
getProviderFromCategory,
|
|
35
|
+
type TooltipSelectItem,
|
|
36
|
+
} from "./menu.js";
|
|
37
|
+
import {
|
|
38
|
+
buildDisplayThemeItems,
|
|
39
|
+
buildThemeActionItems,
|
|
40
|
+
buildRandomDisplay,
|
|
41
|
+
resolveDisplayThemeTarget,
|
|
42
|
+
saveDisplayTheme,
|
|
43
|
+
renameDisplayTheme,
|
|
44
|
+
upsertDisplayTheme,
|
|
45
|
+
} from "./themes.js";
|
|
46
|
+
import {
|
|
47
|
+
buildDisplayShareString,
|
|
48
|
+
buildDisplayShareStringWithoutName,
|
|
49
|
+
decodeDisplayShareString,
|
|
50
|
+
type DecodedDisplayShare,
|
|
51
|
+
} from "../share.js";
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Settings category
|
|
55
|
+
*/
|
|
56
|
+
type ProviderCategory = `provider-${ProviderName}`;
|
|
57
|
+
|
|
58
|
+
type SettingsCategory =
|
|
59
|
+
| "main"
|
|
60
|
+
| "providers"
|
|
61
|
+
| "pin-provider"
|
|
62
|
+
| ProviderCategory
|
|
63
|
+
| "keybindings"
|
|
64
|
+
| "display"
|
|
65
|
+
| "display-theme"
|
|
66
|
+
| "display-theme-save"
|
|
67
|
+
| "display-theme-share"
|
|
68
|
+
| "display-theme-load"
|
|
69
|
+
| "display-theme-action"
|
|
70
|
+
| "display-theme-import"
|
|
71
|
+
| "display-theme-import-action"
|
|
72
|
+
| "display-theme-import-name"
|
|
73
|
+
| "display-theme-rename"
|
|
74
|
+
| "display-theme-random"
|
|
75
|
+
| "display-theme-restore"
|
|
76
|
+
| "display-layout"
|
|
77
|
+
| "display-bar"
|
|
78
|
+
| "display-provider"
|
|
79
|
+
| "display-reset"
|
|
80
|
+
| "display-status"
|
|
81
|
+
| "display-divider"
|
|
82
|
+
| "display-color";
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Show the settings UI
|
|
86
|
+
*/
|
|
87
|
+
export async function showSettingsUI(
|
|
88
|
+
ctx: ExtensionContext,
|
|
89
|
+
options?: {
|
|
90
|
+
coreSettings?: CoreSettings;
|
|
91
|
+
onSettingsChange?: (settings: Settings) => void | Promise<void>;
|
|
92
|
+
onCoreSettingsChange?: (patch: Partial<CoreSettings>, next: CoreSettings) => void | Promise<void>;
|
|
93
|
+
onOpenCoreSettings?: () => void | Promise<void>;
|
|
94
|
+
onDisplayThemeApplied?: (name: string, options?: { source?: "manual" }) => void | Promise<void>;
|
|
95
|
+
onDisplayThemeShared?: (name: string, shareString: string, mode?: "prompt" | "gist" | "string") => void | Promise<void>;
|
|
96
|
+
}
|
|
97
|
+
): Promise<Settings> {
|
|
98
|
+
const onSettingsChange = options?.onSettingsChange;
|
|
99
|
+
const onCoreSettingsChange = options?.onCoreSettingsChange;
|
|
100
|
+
const onOpenCoreSettings = options?.onOpenCoreSettings;
|
|
101
|
+
let settings = getSettings();
|
|
102
|
+
let coreSettings = options?.coreSettings ?? getFallbackCoreSettings(settings);
|
|
103
|
+
const onDisplayThemeApplied = options?.onDisplayThemeApplied;
|
|
104
|
+
const onDisplayThemeShared = options?.onDisplayThemeShared;
|
|
105
|
+
let currentCategory: SettingsCategory = "main";
|
|
106
|
+
|
|
107
|
+
return new Promise((resolve) => {
|
|
108
|
+
ctx.ui.custom<Settings>((tui, theme, _kb, done) => {
|
|
109
|
+
let container = new Container();
|
|
110
|
+
let activeList: SelectList | SettingsList | { handleInput: (data: string) => void } | null = null;
|
|
111
|
+
let themeActionTarget: { id?: string; name: string; display: Settings["display"]; deletable: boolean } | null = null;
|
|
112
|
+
let displayPreviewBackup: Settings["display"] | null = null;
|
|
113
|
+
let randomThemeBackup: Settings["display"] | null = null;
|
|
114
|
+
let displayThemeSelection: string | null = null;
|
|
115
|
+
let pinnedProviderBackup: ProviderName | null | undefined;
|
|
116
|
+
let importCandidate: DecodedDisplayShare | null = null;
|
|
117
|
+
let importBackup: Settings["display"] | null = null;
|
|
118
|
+
let importPendingAction: "save" | "save-apply" | null = null;
|
|
119
|
+
let pendingShare: { name: string; shareString: string; backCategory: SettingsCategory } | null = null;
|
|
120
|
+
const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
121
|
+
|
|
122
|
+
const clamp = (value: number, min: number, max: number): number => Math.min(max, Math.max(min, value));
|
|
123
|
+
|
|
124
|
+
const buildInputSubmenu = (
|
|
125
|
+
label: string,
|
|
126
|
+
parseValue: (value: string) => string | null,
|
|
127
|
+
formatInitial?: (value: string) => string,
|
|
128
|
+
description?: string,
|
|
129
|
+
) => {
|
|
130
|
+
return (currentValue: string, done: (selectedValue?: string) => void) => {
|
|
131
|
+
const input = new Input();
|
|
132
|
+
input.focused = true;
|
|
133
|
+
input.setValue(formatInitial ? formatInitial("") : "");
|
|
134
|
+
input.onSubmit = (value) => {
|
|
135
|
+
const parsed = parseValue(value);
|
|
136
|
+
if (!parsed) return;
|
|
137
|
+
done(parsed);
|
|
138
|
+
};
|
|
139
|
+
input.onEscape = () => {
|
|
140
|
+
done();
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const container = new Container();
|
|
144
|
+
container.addChild(new Text(theme.fg("muted", label), 1, 0));
|
|
145
|
+
if (description) {
|
|
146
|
+
container.addChild(new Text(theme.fg("dim", description), 1, 0));
|
|
147
|
+
}
|
|
148
|
+
container.addChild(new Spacer(1));
|
|
149
|
+
container.addChild(input);
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
render: (width: number) => container.render(width),
|
|
153
|
+
invalidate: () => container.invalidate(),
|
|
154
|
+
handleInput: (data: string) => input.handleInput(data),
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const requestThemeShare = (name: string, shareString: string, backCategory: SettingsCategory) => {
|
|
160
|
+
pendingShare = { name, shareString, backCategory };
|
|
161
|
+
displayThemeSelection = "display-theme-share";
|
|
162
|
+
currentCategory = "display-theme-share";
|
|
163
|
+
rebuild();
|
|
164
|
+
tui.requestRender();
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const parseInteger = (raw: string, min: number, max: number): string | null => {
|
|
168
|
+
const trimmed = raw.trim().replace(/%$/, "");
|
|
169
|
+
if (!trimmed) {
|
|
170
|
+
ctx.ui.notify("Enter a value", "warning");
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const parsed = Number.parseInt(trimmed, 10);
|
|
174
|
+
if (Number.isNaN(parsed)) {
|
|
175
|
+
ctx.ui.notify("Enter a number", "warning");
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
return String(clamp(parsed, min, max));
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const parseBarWidth = (raw: string): string | null => {
|
|
182
|
+
const trimmed = raw.trim().toLowerCase();
|
|
183
|
+
if (!trimmed) {
|
|
184
|
+
ctx.ui.notify("Enter a value", "warning");
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
if (trimmed === "fill") {
|
|
188
|
+
if ((settings.display.widgetPlacement ?? "belowEditor") === "status") {
|
|
189
|
+
ctx.ui.notify("fill is unavailable in status-line placement", "warning");
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
return "fill";
|
|
193
|
+
}
|
|
194
|
+
return parseInteger(trimmed, 0, 100);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const parseDividerBlanks = (raw: string): string | null => {
|
|
198
|
+
const trimmed = raw.trim().toLowerCase();
|
|
199
|
+
if (!trimmed) {
|
|
200
|
+
ctx.ui.notify("Enter a value", "warning");
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
if (trimmed === "fill") return "fill";
|
|
204
|
+
return parseInteger(trimmed, 0, 100);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const parseResetContainment = (raw: string): string | null => {
|
|
208
|
+
const trimmed = raw.trim();
|
|
209
|
+
if (!trimmed) {
|
|
210
|
+
ctx.ui.notify("Enter 1-2 characters", "warning");
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
const normalized = trimmed.toLowerCase();
|
|
214
|
+
if (["none", "blank", "()", "[]", "<>"].includes(normalized)) {
|
|
215
|
+
return normalized;
|
|
216
|
+
}
|
|
217
|
+
const segments = Array.from(segmenter.segment(trimmed), (entry) => entry.segment)
|
|
218
|
+
.map((segment) => segment.trim())
|
|
219
|
+
.filter(Boolean);
|
|
220
|
+
if (segments.length === 0) {
|
|
221
|
+
ctx.ui.notify("Enter 1-2 characters", "warning");
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
const first = segments[0];
|
|
225
|
+
const second = segments[1] ?? first;
|
|
226
|
+
return `${first}${second}`;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const parseDividerCharacter = (raw: string): string | null => {
|
|
230
|
+
const trimmed = raw.trim();
|
|
231
|
+
if (!trimmed) {
|
|
232
|
+
ctx.ui.notify("Enter a character", "warning");
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
const normalized = trimmed.toLowerCase();
|
|
236
|
+
if (normalized === "none" || normalized === "blank") {
|
|
237
|
+
return normalized;
|
|
238
|
+
}
|
|
239
|
+
const iterator = segmenter.segment(trimmed)[Symbol.iterator]();
|
|
240
|
+
const first = iterator.next().value?.segment ?? trimmed[0];
|
|
241
|
+
return first;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const parseBarCharacter = (raw: string): string | null => {
|
|
245
|
+
const trimmed = raw.trim();
|
|
246
|
+
if (!trimmed) {
|
|
247
|
+
ctx.ui.notify("Enter a character", "warning");
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
const normalized = trimmed.toLowerCase();
|
|
251
|
+
if (["light", "heavy", "double", "block"].includes(normalized)) {
|
|
252
|
+
return normalized;
|
|
253
|
+
}
|
|
254
|
+
const segments = Array.from(segmenter.segment(raw), (entry) => entry.segment).filter((segment) => segment !== "\n" && segment !== "\r");
|
|
255
|
+
const first = segments[0] ?? trimmed[0];
|
|
256
|
+
const second = segments[1];
|
|
257
|
+
return second ? `${first}${second}` : first;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const parseStatusIconCustom = (raw: string): string | null => {
|
|
261
|
+
const trimmed = raw.trim();
|
|
262
|
+
if (!trimmed) {
|
|
263
|
+
ctx.ui.notify("Enter four characters", "warning");
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
const segments = Array.from(segmenter.segment(trimmed), (entry) => entry.segment)
|
|
267
|
+
.map((segment) => segment.trim())
|
|
268
|
+
.filter(Boolean);
|
|
269
|
+
if (segments.length < 4) {
|
|
270
|
+
ctx.ui.notify("Enter four characters", "warning");
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
return segments.slice(0, 4).join("");
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const parseProviderLabel = (raw: string): string | null => {
|
|
277
|
+
const trimmed = raw.trim();
|
|
278
|
+
if (!trimmed) {
|
|
279
|
+
ctx.ui.notify("Enter a label", "warning");
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
const normalized = trimmed.toLowerCase();
|
|
283
|
+
if (["none", "plan", "subscription", "sub"].includes(normalized)) {
|
|
284
|
+
return normalized;
|
|
285
|
+
}
|
|
286
|
+
return trimmed;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const attachCustomInputs = (
|
|
290
|
+
items: SettingItem[],
|
|
291
|
+
handlers: Record<string, ReturnType<typeof buildInputSubmenu>>,
|
|
292
|
+
) => {
|
|
293
|
+
for (const item of items) {
|
|
294
|
+
if (!item.values || !item.values.includes(CUSTOM_OPTION)) continue;
|
|
295
|
+
const handler = handlers[item.id];
|
|
296
|
+
if (!handler) continue;
|
|
297
|
+
item.submenu = handler;
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const buildUsageColorSubmenu = () => {
|
|
302
|
+
return (_currentValue: string, done: (selectedValue?: string) => void) => {
|
|
303
|
+
const items = buildUsageColorTargetItems(settings);
|
|
304
|
+
const handleChange = (id: string, value: string) => {
|
|
305
|
+
settings = applyDisplayChange(settings, id, value);
|
|
306
|
+
saveSettings(settings);
|
|
307
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
308
|
+
};
|
|
309
|
+
const list = new SettingsList(
|
|
310
|
+
items,
|
|
311
|
+
Math.min(items.length + 2, 10),
|
|
312
|
+
getSettingsListTheme(),
|
|
313
|
+
handleChange,
|
|
314
|
+
() => {
|
|
315
|
+
done(formatUsageColorTargetsSummary(settings.display.usageColorTargets));
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
return list;
|
|
319
|
+
};
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
function rebuild(): void {
|
|
323
|
+
container = new Container();
|
|
324
|
+
let tooltipText: Text | null = null;
|
|
325
|
+
|
|
326
|
+
const attachTooltip = (items: TooltipSelectItem[], selectList: SelectList): void => {
|
|
327
|
+
if (!items.some((item) => item.tooltip)) return;
|
|
328
|
+
const tooltipComponent = new Text("", 1, 0);
|
|
329
|
+
const setTooltip = (item?: TooltipSelectItem | null) => {
|
|
330
|
+
const tooltip = item?.tooltip?.trim();
|
|
331
|
+
tooltipComponent.setText(tooltip ? theme.fg("dim", tooltip) : "");
|
|
332
|
+
};
|
|
333
|
+
setTooltip(selectList.getSelectedItem() as TooltipSelectItem | null);
|
|
334
|
+
const existingHandler = selectList.onSelectionChange;
|
|
335
|
+
selectList.onSelectionChange = (item) => {
|
|
336
|
+
if (existingHandler) existingHandler(item);
|
|
337
|
+
setTooltip(item as TooltipSelectItem);
|
|
338
|
+
tui.requestRender();
|
|
339
|
+
};
|
|
340
|
+
tooltipText = tooltipComponent;
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// Top border
|
|
344
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
345
|
+
|
|
346
|
+
// Title
|
|
347
|
+
const titles: Record<string, string> = {
|
|
348
|
+
main: "sub-bar Settings",
|
|
349
|
+
providers: "Provider Settings",
|
|
350
|
+
"pin-provider": "Provider Shown",
|
|
351
|
+
keybindings: "Keybindings",
|
|
352
|
+
display: "Adv. Display Settings",
|
|
353
|
+
"display-theme": "Themes",
|
|
354
|
+
"display-theme-save": "Save Theme",
|
|
355
|
+
"display-theme-share": "Share Theme",
|
|
356
|
+
"display-theme-rename": "Rename Theme",
|
|
357
|
+
"display-theme-load": "Load & Manage themes",
|
|
358
|
+
"display-theme-action": "Manage Theme",
|
|
359
|
+
"display-theme-import": "Import Theme",
|
|
360
|
+
"display-theme-import-name": "Name Theme",
|
|
361
|
+
"display-theme-restore": "Restore Theme",
|
|
362
|
+
"display-layout": "Layout & Structure",
|
|
363
|
+
"display-bar": "Bars",
|
|
364
|
+
"display-provider": "Labels & Text",
|
|
365
|
+
"display-reset": "Reset Timer",
|
|
366
|
+
"display-status": "Status",
|
|
367
|
+
"display-divider": "Dividers",
|
|
368
|
+
"display-color": "Colors",
|
|
369
|
+
};
|
|
370
|
+
const providerCategory = getProviderFromCategory(currentCategory);
|
|
371
|
+
let title = providerCategory
|
|
372
|
+
? `${PROVIDER_DISPLAY_NAMES[providerCategory]} Settings`
|
|
373
|
+
: (titles[currentCategory] ?? "sub-bar Settings");
|
|
374
|
+
if (currentCategory === "display-theme-action" && themeActionTarget) {
|
|
375
|
+
title = `Manage ${themeActionTarget.name}`;
|
|
376
|
+
}
|
|
377
|
+
container.addChild(new Text(theme.fg("accent", theme.bold(title)), 1, 0));
|
|
378
|
+
container.addChild(new Spacer(1));
|
|
379
|
+
|
|
380
|
+
if (currentCategory === "main") {
|
|
381
|
+
const items = buildMainMenuItems(settings, settings.pinnedProvider);
|
|
382
|
+
const selectList = new SelectList(items, Math.min(items.length, 10), {
|
|
383
|
+
selectedPrefix: (t: string) => theme.fg("accent", t),
|
|
384
|
+
selectedText: (t: string) => theme.fg("accent", t),
|
|
385
|
+
description: (t: string) => theme.fg("muted", t),
|
|
386
|
+
scrollInfo: (t: string) => theme.fg("dim", t),
|
|
387
|
+
noMatch: (t: string) => theme.fg("warning", t),
|
|
388
|
+
});
|
|
389
|
+
attachTooltip(items, selectList);
|
|
390
|
+
selectList.onSelect = (item) => {
|
|
391
|
+
if (item.value === "open-core-settings") {
|
|
392
|
+
saveSettings(settings);
|
|
393
|
+
done(settings);
|
|
394
|
+
if (onOpenCoreSettings) {
|
|
395
|
+
setTimeout(() => void onOpenCoreSettings(), 0);
|
|
396
|
+
}
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
currentCategory = item.value as SettingsCategory;
|
|
400
|
+
rebuild();
|
|
401
|
+
tui.requestRender();
|
|
402
|
+
};
|
|
403
|
+
selectList.onCancel = () => {
|
|
404
|
+
saveSettings(settings);
|
|
405
|
+
done(settings);
|
|
406
|
+
};
|
|
407
|
+
activeList = selectList;
|
|
408
|
+
container.addChild(selectList);
|
|
409
|
+
} else if (currentCategory === "pin-provider") {
|
|
410
|
+
if (pinnedProviderBackup === undefined) {
|
|
411
|
+
pinnedProviderBackup = settings.pinnedProvider ?? null;
|
|
412
|
+
}
|
|
413
|
+
const orderedProviders = settings.providerOrder.length > 0 ? settings.providerOrder : (Object.keys(settings.providers) as ProviderName[]);
|
|
414
|
+
const items: TooltipSelectItem[] = [
|
|
415
|
+
{
|
|
416
|
+
value: "none",
|
|
417
|
+
label: "Auto",
|
|
418
|
+
description: "current provider",
|
|
419
|
+
tooltip: "Show the current provider automatically.",
|
|
420
|
+
},
|
|
421
|
+
...orderedProviders.map((provider) => ({
|
|
422
|
+
value: provider,
|
|
423
|
+
label: PROVIDER_DISPLAY_NAMES[provider],
|
|
424
|
+
description: provider === settings.pinnedProvider ? "pinned" : "",
|
|
425
|
+
tooltip: `Pin ${PROVIDER_DISPLAY_NAMES[provider]} as the current provider.`,
|
|
426
|
+
})),
|
|
427
|
+
];
|
|
428
|
+
const selectList = new SelectList(items, Math.min(items.length, 10), {
|
|
429
|
+
selectedPrefix: (t: string) => theme.fg("accent", t),
|
|
430
|
+
selectedText: (t: string) => theme.fg("accent", t),
|
|
431
|
+
description: (t: string) => theme.fg("muted", t),
|
|
432
|
+
scrollInfo: (t: string) => theme.fg("dim", t),
|
|
433
|
+
noMatch: (t: string) => theme.fg("warning", t),
|
|
434
|
+
});
|
|
435
|
+
attachTooltip(items, selectList);
|
|
436
|
+
selectList.onSelectionChange = (item) => {
|
|
437
|
+
if (!item) return;
|
|
438
|
+
const nextPinned = item.value === "none" ? null : (item.value as ProviderName);
|
|
439
|
+
if (settings.pinnedProvider === nextPinned) return;
|
|
440
|
+
settings.pinnedProvider = nextPinned;
|
|
441
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
442
|
+
};
|
|
443
|
+
selectList.onSelect = (item) => {
|
|
444
|
+
settings.pinnedProvider = item.value === "none" ? null : (item.value as ProviderName);
|
|
445
|
+
saveSettings(settings);
|
|
446
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
447
|
+
pinnedProviderBackup = undefined;
|
|
448
|
+
currentCategory = "main";
|
|
449
|
+
rebuild();
|
|
450
|
+
tui.requestRender();
|
|
451
|
+
};
|
|
452
|
+
selectList.onCancel = () => {
|
|
453
|
+
if (pinnedProviderBackup !== undefined && settings.pinnedProvider !== pinnedProviderBackup) {
|
|
454
|
+
settings.pinnedProvider = pinnedProviderBackup;
|
|
455
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
456
|
+
}
|
|
457
|
+
pinnedProviderBackup = undefined;
|
|
458
|
+
currentCategory = "main";
|
|
459
|
+
rebuild();
|
|
460
|
+
tui.requestRender();
|
|
461
|
+
};
|
|
462
|
+
activeList = selectList;
|
|
463
|
+
container.addChild(selectList);
|
|
464
|
+
} else if (currentCategory === "keybindings") {
|
|
465
|
+
const parseKeybinding = (raw: string): string | null => {
|
|
466
|
+
const trimmed = raw.trim().toLowerCase();
|
|
467
|
+
if (!trimmed) {
|
|
468
|
+
ctx.ui.notify("Enter a key combo (e.g. ctrl+alt+p) or 'none' to disable", "warning");
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
if (trimmed === "none") return "none";
|
|
472
|
+
const parts = trimmed.split("+");
|
|
473
|
+
const modifiers = new Set(["ctrl", "shift", "alt"]);
|
|
474
|
+
const baseKeys = parts.filter((p) => !modifiers.has(p));
|
|
475
|
+
if (baseKeys.length !== 1) {
|
|
476
|
+
ctx.ui.notify("Invalid key combo. Use format like ctrl+alt+p or ctrl+s", "warning");
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
return trimmed;
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
const kbItems: SettingItem[] = [
|
|
483
|
+
{
|
|
484
|
+
id: "cycleProvider",
|
|
485
|
+
label: "Cycle Provider",
|
|
486
|
+
currentValue: settings.keybindings.cycleProvider,
|
|
487
|
+
description: "Shortcut to cycle through providers. Changes take effect after pi restart.",
|
|
488
|
+
submenu: buildInputSubmenu(
|
|
489
|
+
"Cycle Provider shortcut",
|
|
490
|
+
parseKeybinding,
|
|
491
|
+
undefined,
|
|
492
|
+
"Enter a key combo (e.g. ctrl+alt+p) or 'none' to disable.",
|
|
493
|
+
),
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
id: "toggleResetFormat",
|
|
497
|
+
label: "Toggle Reset Format",
|
|
498
|
+
currentValue: settings.keybindings.toggleResetFormat,
|
|
499
|
+
description: "Shortcut to toggle reset timer format. Changes take effect after pi restart.",
|
|
500
|
+
submenu: buildInputSubmenu(
|
|
501
|
+
"Toggle Reset Format shortcut",
|
|
502
|
+
parseKeybinding,
|
|
503
|
+
undefined,
|
|
504
|
+
"Enter a key combo (e.g. ctrl+alt+r) or 'none' to disable.",
|
|
505
|
+
),
|
|
506
|
+
},
|
|
507
|
+
];
|
|
508
|
+
|
|
509
|
+
const handleKbChange = (id: string, value: string) => {
|
|
510
|
+
if (id === "cycleProvider" || id === "toggleResetFormat") {
|
|
511
|
+
settings.keybindings = { ...settings.keybindings, [id]: value };
|
|
512
|
+
saveSettings(settings);
|
|
513
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
const kbList = new SettingsList(
|
|
518
|
+
kbItems,
|
|
519
|
+
Math.min(kbItems.length + 3, 10),
|
|
520
|
+
getSettingsListTheme(),
|
|
521
|
+
handleKbChange,
|
|
522
|
+
() => {
|
|
523
|
+
currentCategory = "main";
|
|
524
|
+
rebuild();
|
|
525
|
+
tui.requestRender();
|
|
526
|
+
},
|
|
527
|
+
);
|
|
528
|
+
activeList = kbList;
|
|
529
|
+
container.addChild(kbList);
|
|
530
|
+
} else if (currentCategory === "providers") {
|
|
531
|
+
const items = buildProviderListItems(settings, coreSettings.providers);
|
|
532
|
+
const selectList = new SelectList(items, Math.min(items.length, 10), {
|
|
533
|
+
selectedPrefix: (t: string) => theme.fg("accent", t),
|
|
534
|
+
selectedText: (t: string) => theme.fg("accent", t),
|
|
535
|
+
description: (t: string) => theme.fg("muted", t),
|
|
536
|
+
scrollInfo: (t: string) => theme.fg("dim", t),
|
|
537
|
+
noMatch: (t: string) => theme.fg("warning", t),
|
|
538
|
+
});
|
|
539
|
+
attachTooltip(items, selectList);
|
|
540
|
+
selectList.onSelect = (item) => {
|
|
541
|
+
if (item.value === "reset-providers") {
|
|
542
|
+
const defaults = getDefaultSettings();
|
|
543
|
+
settings.providers = { ...defaults.providers };
|
|
544
|
+
saveSettings(settings);
|
|
545
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
546
|
+
ctx.ui.notify("Provider settings reset to defaults", "info");
|
|
547
|
+
rebuild();
|
|
548
|
+
tui.requestRender();
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
currentCategory = item.value as SettingsCategory;
|
|
552
|
+
rebuild();
|
|
553
|
+
tui.requestRender();
|
|
554
|
+
};
|
|
555
|
+
selectList.onCancel = () => {
|
|
556
|
+
currentCategory = "main";
|
|
557
|
+
rebuild();
|
|
558
|
+
tui.requestRender();
|
|
559
|
+
};
|
|
560
|
+
activeList = selectList;
|
|
561
|
+
container.addChild(selectList);
|
|
562
|
+
} else if (providerCategory) {
|
|
563
|
+
const items = buildProviderSettingsItems(settings, providerCategory);
|
|
564
|
+
const coreProvider = coreSettings.providers[providerCategory];
|
|
565
|
+
const enabledValue = coreProvider.enabled === "auto"
|
|
566
|
+
? "auto"
|
|
567
|
+
: coreProvider.enabled === true || coreProvider.enabled === "on"
|
|
568
|
+
? "on"
|
|
569
|
+
: "off";
|
|
570
|
+
items.unshift({
|
|
571
|
+
id: "enabled",
|
|
572
|
+
label: "Enabled",
|
|
573
|
+
currentValue: enabledValue,
|
|
574
|
+
values: ["auto", "on", "off"],
|
|
575
|
+
description: "Auto enables if credentials are detected.",
|
|
576
|
+
});
|
|
577
|
+
const handleChange = (id: string, value: string) => {
|
|
578
|
+
if (id === "enabled") {
|
|
579
|
+
const nextEnabled = value === "auto" ? "auto" : value === "on";
|
|
580
|
+
coreProvider.enabled = nextEnabled;
|
|
581
|
+
if (onCoreSettingsChange) {
|
|
582
|
+
const patch = {
|
|
583
|
+
providers: {
|
|
584
|
+
[providerCategory]: { enabled: nextEnabled },
|
|
585
|
+
},
|
|
586
|
+
} as unknown as Partial<CoreSettings>;
|
|
587
|
+
void onCoreSettingsChange(patch, coreSettings);
|
|
588
|
+
}
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
settings = applyProviderSettingsChange(settings, providerCategory, id, value);
|
|
592
|
+
saveSettings(settings);
|
|
593
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
594
|
+
};
|
|
595
|
+
const settingsHintText = "↓ navigate • ←/→ change • Enter/Space edit custom • Esc to cancel";
|
|
596
|
+
const customTheme = {
|
|
597
|
+
...getSettingsListTheme(),
|
|
598
|
+
hint: (text: string) => {
|
|
599
|
+
if (text.includes("Enter/Space")) {
|
|
600
|
+
return theme.fg("dim", settingsHintText);
|
|
601
|
+
}
|
|
602
|
+
return theme.fg("dim", text);
|
|
603
|
+
},
|
|
604
|
+
};
|
|
605
|
+
const settingsList = new SettingsList(
|
|
606
|
+
items,
|
|
607
|
+
Math.min(items.length + 2, 15),
|
|
608
|
+
customTheme,
|
|
609
|
+
handleChange,
|
|
610
|
+
() => {
|
|
611
|
+
currentCategory = "providers";
|
|
612
|
+
rebuild();
|
|
613
|
+
tui.requestRender();
|
|
614
|
+
}
|
|
615
|
+
);
|
|
616
|
+
activeList = settingsList;
|
|
617
|
+
container.addChild(settingsList);
|
|
618
|
+
} else if (currentCategory === "display") {
|
|
619
|
+
const items = buildDisplayMenuItems();
|
|
620
|
+
const selectList = new SelectList(items, Math.min(items.length, 10), {
|
|
621
|
+
selectedPrefix: (t: string) => theme.fg("accent", t),
|
|
622
|
+
selectedText: (t: string) => theme.fg("accent", t),
|
|
623
|
+
description: (t: string) => theme.fg("muted", t),
|
|
624
|
+
scrollInfo: (t: string) => theme.fg("dim", t),
|
|
625
|
+
noMatch: (t: string) => theme.fg("warning", t),
|
|
626
|
+
});
|
|
627
|
+
attachTooltip(items, selectList);
|
|
628
|
+
selectList.onSelect = (item) => {
|
|
629
|
+
currentCategory = item.value as SettingsCategory;
|
|
630
|
+
rebuild();
|
|
631
|
+
tui.requestRender();
|
|
632
|
+
};
|
|
633
|
+
selectList.onCancel = () => {
|
|
634
|
+
currentCategory = "main";
|
|
635
|
+
rebuild();
|
|
636
|
+
tui.requestRender();
|
|
637
|
+
};
|
|
638
|
+
activeList = selectList;
|
|
639
|
+
container.addChild(selectList);
|
|
640
|
+
} else if (currentCategory === "display-theme") {
|
|
641
|
+
const items = buildDisplayThemeMenuItems();
|
|
642
|
+
const selectList = new SelectList(items, Math.min(items.length, 10), {
|
|
643
|
+
selectedPrefix: (t: string) => theme.fg("accent", t),
|
|
644
|
+
selectedText: (t: string) => theme.fg("accent", t),
|
|
645
|
+
description: (t: string) => theme.fg("muted", t),
|
|
646
|
+
scrollInfo: (t: string) => theme.fg("dim", t),
|
|
647
|
+
noMatch: (t: string) => theme.fg("warning", t),
|
|
648
|
+
});
|
|
649
|
+
if (displayThemeSelection) {
|
|
650
|
+
const index = items.findIndex((item) => item.value === displayThemeSelection);
|
|
651
|
+
if (index >= 0) {
|
|
652
|
+
selectList.setSelectedIndex(index);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
attachTooltip(items, selectList);
|
|
656
|
+
selectList.onSelect = (item) => {
|
|
657
|
+
displayThemeSelection = item.value;
|
|
658
|
+
currentCategory = item.value as SettingsCategory;
|
|
659
|
+
pendingShare = null;
|
|
660
|
+
rebuild();
|
|
661
|
+
tui.requestRender();
|
|
662
|
+
};
|
|
663
|
+
selectList.onCancel = () => {
|
|
664
|
+
currentCategory = "display";
|
|
665
|
+
pendingShare = null;
|
|
666
|
+
rebuild();
|
|
667
|
+
tui.requestRender();
|
|
668
|
+
};
|
|
669
|
+
activeList = selectList;
|
|
670
|
+
container.addChild(selectList);
|
|
671
|
+
} else if (currentCategory === "display-theme-save") {
|
|
672
|
+
const input = new Input();
|
|
673
|
+
input.focused = true;
|
|
674
|
+
const titleText = new Text(theme.fg("muted", "Theme name"), 1, 0);
|
|
675
|
+
input.onSubmit = (value) => {
|
|
676
|
+
const trimmed = value.trim();
|
|
677
|
+
if (!trimmed) {
|
|
678
|
+
ctx.ui.notify("Enter a theme name", "warning");
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
settings = saveDisplayTheme(settings, trimmed);
|
|
682
|
+
saveSettings(settings);
|
|
683
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
684
|
+
ctx.ui.notify(`Theme ${trimmed} saved`, "info");
|
|
685
|
+
const shareString = buildDisplayShareString(trimmed, settings.display);
|
|
686
|
+
if (onDisplayThemeShared) {
|
|
687
|
+
requestThemeShare(trimmed, shareString, "display-theme");
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
ctx.ui.notify(shareString, "info");
|
|
691
|
+
currentCategory = "display-theme";
|
|
692
|
+
rebuild();
|
|
693
|
+
tui.requestRender();
|
|
694
|
+
};
|
|
695
|
+
input.onEscape = () => {
|
|
696
|
+
currentCategory = "display-theme";
|
|
697
|
+
rebuild();
|
|
698
|
+
tui.requestRender();
|
|
699
|
+
};
|
|
700
|
+
container.addChild(titleText);
|
|
701
|
+
container.addChild(new Spacer(1));
|
|
702
|
+
container.addChild(input);
|
|
703
|
+
activeList = input;
|
|
704
|
+
} else if (currentCategory === "display-theme-share") {
|
|
705
|
+
displayThemeSelection = "display-theme-share";
|
|
706
|
+
const shareTarget = pendingShare ?? {
|
|
707
|
+
name: "",
|
|
708
|
+
shareString: buildDisplayShareStringWithoutName(settings.display),
|
|
709
|
+
backCategory: "display-theme" as SettingsCategory,
|
|
710
|
+
};
|
|
711
|
+
pendingShare = shareTarget;
|
|
712
|
+
|
|
713
|
+
const shareItems: TooltipSelectItem[] = [
|
|
714
|
+
{
|
|
715
|
+
value: "gist",
|
|
716
|
+
label: "Upload secret gist",
|
|
717
|
+
description: "share via GitHub gist",
|
|
718
|
+
tooltip: "Create a secret GitHub gist using the gh CLI.",
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
value: "string",
|
|
722
|
+
label: "Post share string",
|
|
723
|
+
description: "share in chat",
|
|
724
|
+
tooltip: "Post the raw share string to chat.",
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
value: "cancel",
|
|
728
|
+
label: "Cancel",
|
|
729
|
+
description: "discard share",
|
|
730
|
+
tooltip: "Cancel without sharing.",
|
|
731
|
+
},
|
|
732
|
+
];
|
|
733
|
+
|
|
734
|
+
const selectList = new SelectList(shareItems, shareItems.length, {
|
|
735
|
+
selectedPrefix: (t: string) => theme.fg("accent", t),
|
|
736
|
+
selectedText: (t: string) => theme.fg("accent", t),
|
|
737
|
+
description: (t: string) => theme.fg("muted", t),
|
|
738
|
+
scrollInfo: (t: string) => theme.fg("dim", t),
|
|
739
|
+
noMatch: (t: string) => theme.fg("warning", t),
|
|
740
|
+
});
|
|
741
|
+
attachTooltip(shareItems, selectList);
|
|
742
|
+
|
|
743
|
+
selectList.onSelect = (item) => {
|
|
744
|
+
if (item.value === "gist" || item.value === "string") {
|
|
745
|
+
if (onDisplayThemeShared) {
|
|
746
|
+
void onDisplayThemeShared(shareTarget.name, shareTarget.shareString, item.value as "gist" | "string");
|
|
747
|
+
} else {
|
|
748
|
+
ctx.ui.notify(shareTarget.shareString, "info");
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
pendingShare = null;
|
|
752
|
+
currentCategory = shareTarget.backCategory;
|
|
753
|
+
rebuild();
|
|
754
|
+
tui.requestRender();
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
selectList.onCancel = () => {
|
|
758
|
+
pendingShare = null;
|
|
759
|
+
currentCategory = shareTarget.backCategory;
|
|
760
|
+
rebuild();
|
|
761
|
+
tui.requestRender();
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
activeList = selectList;
|
|
765
|
+
container.addChild(selectList);
|
|
766
|
+
} else if (currentCategory === "display-theme-load") {
|
|
767
|
+
if (!displayPreviewBackup) {
|
|
768
|
+
displayPreviewBackup = { ...settings.display };
|
|
769
|
+
}
|
|
770
|
+
const defaults = getDefaultSettings();
|
|
771
|
+
const fallbackUser = settings.displayUserTheme ?? displayPreviewBackup;
|
|
772
|
+
const themeItems = buildDisplayThemeItems(settings);
|
|
773
|
+
|
|
774
|
+
const selectList = new SelectList(themeItems, Math.min(themeItems.length, 10), {
|
|
775
|
+
selectedPrefix: (t: string) => theme.fg("accent", t),
|
|
776
|
+
selectedText: (t: string) => theme.fg("accent", t),
|
|
777
|
+
description: (t: string) => theme.fg("muted", t),
|
|
778
|
+
scrollInfo: (t: string) => theme.fg("dim", t),
|
|
779
|
+
noMatch: (t: string) => theme.fg("warning", t),
|
|
780
|
+
});
|
|
781
|
+
selectList.onSelectionChange = (item) => {
|
|
782
|
+
if (!item) return;
|
|
783
|
+
const target = resolveDisplayThemeTarget(item.value, settings, defaults, fallbackUser);
|
|
784
|
+
if (!target) return;
|
|
785
|
+
settings.display = { ...target.display };
|
|
786
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
787
|
+
tui.requestRender();
|
|
788
|
+
};
|
|
789
|
+
attachTooltip(themeItems, selectList);
|
|
790
|
+
|
|
791
|
+
selectList.onSelect = (item) => {
|
|
792
|
+
const target = resolveDisplayThemeTarget(item.value, settings, defaults, fallbackUser);
|
|
793
|
+
if (!target) return;
|
|
794
|
+
if (item.value.startsWith("theme:")) {
|
|
795
|
+
themeActionTarget = target;
|
|
796
|
+
currentCategory = "display-theme-action";
|
|
797
|
+
rebuild();
|
|
798
|
+
tui.requestRender();
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
const backup = displayPreviewBackup ?? settings.display;
|
|
803
|
+
settings.displayUserTheme = { ...backup };
|
|
804
|
+
settings.display = { ...target.display };
|
|
805
|
+
saveSettings(settings);
|
|
806
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
807
|
+
if (onDisplayThemeApplied) void onDisplayThemeApplied(target.name, { source: "manual" });
|
|
808
|
+
displayPreviewBackup = null;
|
|
809
|
+
currentCategory = "display-theme";
|
|
810
|
+
rebuild();
|
|
811
|
+
tui.requestRender();
|
|
812
|
+
};
|
|
813
|
+
selectList.onCancel = () => {
|
|
814
|
+
if (displayPreviewBackup) {
|
|
815
|
+
settings.display = { ...displayPreviewBackup };
|
|
816
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
817
|
+
}
|
|
818
|
+
displayPreviewBackup = null;
|
|
819
|
+
currentCategory = "display-theme";
|
|
820
|
+
rebuild();
|
|
821
|
+
tui.requestRender();
|
|
822
|
+
};
|
|
823
|
+
activeList = selectList;
|
|
824
|
+
container.addChild(selectList);
|
|
825
|
+
} else if (currentCategory === "display-theme-random") {
|
|
826
|
+
if (!randomThemeBackup) {
|
|
827
|
+
randomThemeBackup = { ...settings.display };
|
|
828
|
+
settings.displayUserTheme = { ...randomThemeBackup };
|
|
829
|
+
}
|
|
830
|
+
displayThemeSelection = "display-theme-random";
|
|
831
|
+
const randomDisplay = buildRandomDisplay(settings.display);
|
|
832
|
+
settings.display = { ...randomDisplay };
|
|
833
|
+
saveSettings(settings);
|
|
834
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
835
|
+
currentCategory = "display-theme";
|
|
836
|
+
rebuild();
|
|
837
|
+
tui.requestRender();
|
|
838
|
+
} else if (currentCategory === "display-theme-restore") {
|
|
839
|
+
displayThemeSelection = "display-theme-restore";
|
|
840
|
+
const defaults = getDefaultSettings();
|
|
841
|
+
const fallbackUser = settings.displayUserTheme ?? settings.display;
|
|
842
|
+
const target = resolveDisplayThemeTarget("user", settings, defaults, fallbackUser);
|
|
843
|
+
if (target) {
|
|
844
|
+
const backup = displayPreviewBackup ?? settings.display;
|
|
845
|
+
settings.displayUserTheme = { ...backup };
|
|
846
|
+
settings.display = { ...target.display };
|
|
847
|
+
saveSettings(settings);
|
|
848
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
849
|
+
if (onDisplayThemeApplied) void onDisplayThemeApplied(target.name, { source: "manual" });
|
|
850
|
+
displayPreviewBackup = null;
|
|
851
|
+
}
|
|
852
|
+
currentCategory = "display-theme";
|
|
853
|
+
rebuild();
|
|
854
|
+
tui.requestRender();
|
|
855
|
+
} else if (currentCategory === "display-theme-import") {
|
|
856
|
+
const input = new Input();
|
|
857
|
+
input.focused = true;
|
|
858
|
+
const titleText = new Text(theme.fg("muted", "Paste Theme Share string"), 1, 0);
|
|
859
|
+
input.onSubmit = (value) => {
|
|
860
|
+
const trimmed = value.trim();
|
|
861
|
+
if (!trimmed) {
|
|
862
|
+
ctx.ui.notify("Enter a theme share string", "warning");
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
const decoded = decodeDisplayShareString(trimmed);
|
|
866
|
+
if (!decoded) {
|
|
867
|
+
ctx.ui.notify("Invalid theme share string", "error");
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
if (!displayPreviewBackup) {
|
|
871
|
+
displayPreviewBackup = { ...settings.display };
|
|
872
|
+
}
|
|
873
|
+
importBackup = displayPreviewBackup;
|
|
874
|
+
importCandidate = decoded;
|
|
875
|
+
settings.display = { ...decoded.display };
|
|
876
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
877
|
+
currentCategory = "display-theme-import-action";
|
|
878
|
+
rebuild();
|
|
879
|
+
tui.requestRender();
|
|
880
|
+
};
|
|
881
|
+
input.onEscape = () => {
|
|
882
|
+
currentCategory = "display-theme-load";
|
|
883
|
+
rebuild();
|
|
884
|
+
tui.requestRender();
|
|
885
|
+
};
|
|
886
|
+
container.addChild(titleText);
|
|
887
|
+
container.addChild(new Spacer(1));
|
|
888
|
+
container.addChild(input);
|
|
889
|
+
activeList = input;
|
|
890
|
+
} else if (currentCategory === "display-theme-import-action") {
|
|
891
|
+
const candidate = importCandidate;
|
|
892
|
+
if (!candidate) {
|
|
893
|
+
currentCategory = "display-theme-load";
|
|
894
|
+
rebuild();
|
|
895
|
+
tui.requestRender();
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
const importItems: TooltipSelectItem[] = [
|
|
900
|
+
{
|
|
901
|
+
value: "save-apply",
|
|
902
|
+
label: "Save & apply",
|
|
903
|
+
description: "save and use this theme",
|
|
904
|
+
tooltip: "Save the theme and keep it applied.",
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
value: "save",
|
|
908
|
+
label: "Save",
|
|
909
|
+
description: "save without applying",
|
|
910
|
+
tooltip: "Save the theme and restore the previous display.",
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
value: "cancel",
|
|
914
|
+
label: "Cancel",
|
|
915
|
+
description: "discard import",
|
|
916
|
+
tooltip: "Discard and restore the previous display.",
|
|
917
|
+
},
|
|
918
|
+
];
|
|
919
|
+
|
|
920
|
+
const notifyImported = (name: string) => {
|
|
921
|
+
const message = candidate.isNewerVersion
|
|
922
|
+
? `Imported ${name} (newer version, some fields may be ignored)`
|
|
923
|
+
: `Imported ${name}`;
|
|
924
|
+
ctx.ui.notify(message, candidate.isNewerVersion ? "warning" : "info");
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
const restoreBackup = () => {
|
|
928
|
+
if (importBackup) {
|
|
929
|
+
settings.display = { ...importBackup };
|
|
930
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
const selectList = new SelectList(importItems, importItems.length, {
|
|
935
|
+
selectedPrefix: (t: string) => theme.fg("accent", t),
|
|
936
|
+
selectedText: (t: string) => theme.fg("accent", t),
|
|
937
|
+
description: (t: string) => theme.fg("muted", t),
|
|
938
|
+
scrollInfo: (t: string) => theme.fg("dim", t),
|
|
939
|
+
noMatch: (t: string) => theme.fg("warning", t),
|
|
940
|
+
});
|
|
941
|
+
selectList.onSelectionChange = (item) => {
|
|
942
|
+
if (!item) return;
|
|
943
|
+
if (item.value === "save-apply") {
|
|
944
|
+
settings.display = { ...candidate.display };
|
|
945
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
restoreBackup();
|
|
949
|
+
};
|
|
950
|
+
attachTooltip(importItems, selectList);
|
|
951
|
+
|
|
952
|
+
selectList.onSelect = (item) => {
|
|
953
|
+
if ((item.value === "save-apply" || item.value === "save") && !candidate.hasName) {
|
|
954
|
+
importPendingAction = item.value as "save" | "save-apply";
|
|
955
|
+
currentCategory = "display-theme-import-name";
|
|
956
|
+
rebuild();
|
|
957
|
+
tui.requestRender();
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
if (item.value === "save-apply") {
|
|
961
|
+
const resolvedName = candidate.name;
|
|
962
|
+
if (importBackup) {
|
|
963
|
+
settings.displayUserTheme = { ...importBackup };
|
|
964
|
+
}
|
|
965
|
+
settings = upsertDisplayTheme(settings, resolvedName, candidate.display, "imported");
|
|
966
|
+
settings.display = { ...candidate.display };
|
|
967
|
+
saveSettings(settings);
|
|
968
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
969
|
+
if (onDisplayThemeApplied) void onDisplayThemeApplied(resolvedName, { source: "manual" });
|
|
970
|
+
notifyImported(resolvedName);
|
|
971
|
+
displayPreviewBackup = null;
|
|
972
|
+
importCandidate = null;
|
|
973
|
+
importBackup = null;
|
|
974
|
+
importPendingAction = null;
|
|
975
|
+
currentCategory = "display-theme";
|
|
976
|
+
rebuild();
|
|
977
|
+
tui.requestRender();
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
if (item.value === "save") {
|
|
981
|
+
const resolvedName = candidate.name;
|
|
982
|
+
settings = upsertDisplayTheme(settings, resolvedName, candidate.display, "imported");
|
|
983
|
+
restoreBackup();
|
|
984
|
+
saveSettings(settings);
|
|
985
|
+
notifyImported(resolvedName);
|
|
986
|
+
importCandidate = null;
|
|
987
|
+
importBackup = null;
|
|
988
|
+
importPendingAction = null;
|
|
989
|
+
currentCategory = "display-theme-load";
|
|
990
|
+
rebuild();
|
|
991
|
+
tui.requestRender();
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
restoreBackup();
|
|
995
|
+
importCandidate = null;
|
|
996
|
+
importBackup = null;
|
|
997
|
+
importPendingAction = null;
|
|
998
|
+
currentCategory = "display-theme-load";
|
|
999
|
+
rebuild();
|
|
1000
|
+
tui.requestRender();
|
|
1001
|
+
};
|
|
1002
|
+
selectList.onCancel = () => {
|
|
1003
|
+
restoreBackup();
|
|
1004
|
+
importCandidate = null;
|
|
1005
|
+
importBackup = null;
|
|
1006
|
+
importPendingAction = null;
|
|
1007
|
+
currentCategory = "display-theme-load";
|
|
1008
|
+
rebuild();
|
|
1009
|
+
tui.requestRender();
|
|
1010
|
+
};
|
|
1011
|
+
activeList = selectList;
|
|
1012
|
+
container.addChild(selectList);
|
|
1013
|
+
} else if (currentCategory === "display-theme-import-name") {
|
|
1014
|
+
const candidate = importCandidate;
|
|
1015
|
+
if (!candidate) {
|
|
1016
|
+
currentCategory = "display-theme-load";
|
|
1017
|
+
rebuild();
|
|
1018
|
+
tui.requestRender();
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
const notifyImported = (name: string) => {
|
|
1023
|
+
const message = candidate.isNewerVersion
|
|
1024
|
+
? `Imported ${name} (newer version, some fields may be ignored)`
|
|
1025
|
+
: `Imported ${name}`;
|
|
1026
|
+
ctx.ui.notify(message, candidate.isNewerVersion ? "warning" : "info");
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
const restoreBackup = () => {
|
|
1030
|
+
if (importBackup) {
|
|
1031
|
+
settings.display = { ...importBackup };
|
|
1032
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
const input = new Input();
|
|
1037
|
+
input.focused = true;
|
|
1038
|
+
const titleText = new Text(theme.fg("muted", "Theme name"), 1, 0);
|
|
1039
|
+
input.onSubmit = (value) => {
|
|
1040
|
+
const trimmed = value.trim();
|
|
1041
|
+
if (!trimmed) {
|
|
1042
|
+
ctx.ui.notify("Enter a theme name", "warning");
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
const applyImport = importPendingAction === "save-apply";
|
|
1046
|
+
if (applyImport && importBackup) {
|
|
1047
|
+
settings.displayUserTheme = { ...importBackup };
|
|
1048
|
+
}
|
|
1049
|
+
settings = upsertDisplayTheme(settings, trimmed, candidate.display, "imported");
|
|
1050
|
+
if (applyImport) {
|
|
1051
|
+
settings.display = { ...candidate.display };
|
|
1052
|
+
} else {
|
|
1053
|
+
restoreBackup();
|
|
1054
|
+
}
|
|
1055
|
+
saveSettings(settings);
|
|
1056
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
1057
|
+
if (applyImport && onDisplayThemeApplied) {
|
|
1058
|
+
void onDisplayThemeApplied(trimmed, { source: "manual" });
|
|
1059
|
+
}
|
|
1060
|
+
notifyImported(trimmed);
|
|
1061
|
+
displayPreviewBackup = null;
|
|
1062
|
+
importCandidate = null;
|
|
1063
|
+
importBackup = null;
|
|
1064
|
+
importPendingAction = null;
|
|
1065
|
+
currentCategory = applyImport ? "display-theme" : "display-theme-load";
|
|
1066
|
+
rebuild();
|
|
1067
|
+
tui.requestRender();
|
|
1068
|
+
};
|
|
1069
|
+
input.onEscape = () => {
|
|
1070
|
+
importPendingAction = null;
|
|
1071
|
+
currentCategory = "display-theme-import-action";
|
|
1072
|
+
rebuild();
|
|
1073
|
+
tui.requestRender();
|
|
1074
|
+
};
|
|
1075
|
+
container.addChild(titleText);
|
|
1076
|
+
container.addChild(new Spacer(1));
|
|
1077
|
+
container.addChild(input);
|
|
1078
|
+
activeList = input;
|
|
1079
|
+
} else if (currentCategory === "display-theme-rename") {
|
|
1080
|
+
const target = themeActionTarget;
|
|
1081
|
+
if (!target || !target.id) {
|
|
1082
|
+
currentCategory = "display-theme-load";
|
|
1083
|
+
rebuild();
|
|
1084
|
+
tui.requestRender();
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
const input = new Input();
|
|
1089
|
+
input.focused = true;
|
|
1090
|
+
const titleText = new Text(theme.fg("muted", `Rename ${target.name}`), 1, 0);
|
|
1091
|
+
input.onSubmit = (value) => {
|
|
1092
|
+
const trimmed = value.trim();
|
|
1093
|
+
if (!trimmed) {
|
|
1094
|
+
ctx.ui.notify("Enter a theme name", "warning");
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
settings = renameDisplayTheme(settings, target.id!, trimmed);
|
|
1098
|
+
saveSettings(settings);
|
|
1099
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
1100
|
+
themeActionTarget = null;
|
|
1101
|
+
currentCategory = "display-theme-load";
|
|
1102
|
+
rebuild();
|
|
1103
|
+
tui.requestRender();
|
|
1104
|
+
};
|
|
1105
|
+
input.onEscape = () => {
|
|
1106
|
+
currentCategory = "display-theme-action";
|
|
1107
|
+
rebuild();
|
|
1108
|
+
tui.requestRender();
|
|
1109
|
+
};
|
|
1110
|
+
container.addChild(titleText);
|
|
1111
|
+
container.addChild(new Spacer(1));
|
|
1112
|
+
container.addChild(input);
|
|
1113
|
+
activeList = input;
|
|
1114
|
+
} else if (currentCategory === "display-theme-action") {
|
|
1115
|
+
const target = themeActionTarget;
|
|
1116
|
+
if (!target) {
|
|
1117
|
+
currentCategory = "display-theme-load";
|
|
1118
|
+
rebuild();
|
|
1119
|
+
tui.requestRender();
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
const items = buildThemeActionItems(target);
|
|
1124
|
+
|
|
1125
|
+
const selectList = new SelectList(items, items.length, {
|
|
1126
|
+
selectedPrefix: (t: string) => theme.fg("accent", t),
|
|
1127
|
+
selectedText: (t: string) => theme.fg("accent", t),
|
|
1128
|
+
description: (t: string) => theme.fg("muted", t),
|
|
1129
|
+
scrollInfo: (t: string) => theme.fg("dim", t),
|
|
1130
|
+
noMatch: (t: string) => theme.fg("warning", t),
|
|
1131
|
+
});
|
|
1132
|
+
attachTooltip(items, selectList);
|
|
1133
|
+
|
|
1134
|
+
selectList.onSelect = (item) => {
|
|
1135
|
+
if (item.value === "load") {
|
|
1136
|
+
const backup = displayPreviewBackup ?? settings.display;
|
|
1137
|
+
settings.displayUserTheme = { ...backup };
|
|
1138
|
+
settings.display = { ...target.display };
|
|
1139
|
+
saveSettings(settings);
|
|
1140
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
1141
|
+
if (onDisplayThemeApplied) void onDisplayThemeApplied(target.name, { source: "manual" });
|
|
1142
|
+
displayPreviewBackup = null;
|
|
1143
|
+
themeActionTarget = null;
|
|
1144
|
+
currentCategory = "display-theme";
|
|
1145
|
+
rebuild();
|
|
1146
|
+
tui.requestRender();
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
if (item.value === "share") {
|
|
1150
|
+
const shareString = buildDisplayShareString(target.name, target.display);
|
|
1151
|
+
if (onDisplayThemeShared) {
|
|
1152
|
+
requestThemeShare(target.name, shareString, "display-theme-load");
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
ctx.ui.notify(shareString, "info");
|
|
1156
|
+
themeActionTarget = null;
|
|
1157
|
+
currentCategory = "display-theme-load";
|
|
1158
|
+
rebuild();
|
|
1159
|
+
tui.requestRender();
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
if (item.value === "rename" && target.deletable && target.id) {
|
|
1163
|
+
currentCategory = "display-theme-rename";
|
|
1164
|
+
rebuild();
|
|
1165
|
+
tui.requestRender();
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
if (item.value === "delete" && target.deletable && target.id) {
|
|
1169
|
+
settings.displayThemes = settings.displayThemes.filter((entry) => entry.id !== target.id);
|
|
1170
|
+
saveSettings(settings);
|
|
1171
|
+
if (displayPreviewBackup) {
|
|
1172
|
+
settings.display = { ...displayPreviewBackup };
|
|
1173
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
1174
|
+
}
|
|
1175
|
+
themeActionTarget = null;
|
|
1176
|
+
currentCategory = "display-theme-load";
|
|
1177
|
+
rebuild();
|
|
1178
|
+
tui.requestRender();
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
selectList.onCancel = () => {
|
|
1182
|
+
currentCategory = "display-theme-load";
|
|
1183
|
+
rebuild();
|
|
1184
|
+
tui.requestRender();
|
|
1185
|
+
};
|
|
1186
|
+
activeList = selectList;
|
|
1187
|
+
container.addChild(selectList);
|
|
1188
|
+
} else {
|
|
1189
|
+
// Settings list for category
|
|
1190
|
+
let items: SettingItem[];
|
|
1191
|
+
let handleChange: (id: string, value: string) => void;
|
|
1192
|
+
let backCategory: SettingsCategory = "display";
|
|
1193
|
+
|
|
1194
|
+
switch (currentCategory) {
|
|
1195
|
+
case "display-layout":
|
|
1196
|
+
items = buildDisplayLayoutItems(settings);
|
|
1197
|
+
break;
|
|
1198
|
+
case "display-bar":
|
|
1199
|
+
items = buildDisplayBarItems(settings);
|
|
1200
|
+
break;
|
|
1201
|
+
case "display-provider":
|
|
1202
|
+
items = buildDisplayProviderItems(settings);
|
|
1203
|
+
break;
|
|
1204
|
+
case "display-reset":
|
|
1205
|
+
items = buildDisplayResetItems(settings);
|
|
1206
|
+
break;
|
|
1207
|
+
case "display-status":
|
|
1208
|
+
items = buildDisplayStatusItems(settings);
|
|
1209
|
+
break;
|
|
1210
|
+
case "display-divider":
|
|
1211
|
+
items = buildDisplayDividerItems(settings);
|
|
1212
|
+
break;
|
|
1213
|
+
case "display-color":
|
|
1214
|
+
items = buildDisplayColorItems(settings);
|
|
1215
|
+
break;
|
|
1216
|
+
default:
|
|
1217
|
+
items = [];
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
const customHandlers: Record<string, ReturnType<typeof buildInputSubmenu>> = {};
|
|
1221
|
+
if (currentCategory === "display-layout") {
|
|
1222
|
+
customHandlers.paddingLeft = buildInputSubmenu("Padding Left", (value) => parseInteger(value, 0, 100));
|
|
1223
|
+
customHandlers.paddingRight = buildInputSubmenu("Padding Right", (value) => parseInteger(value, 0, 100));
|
|
1224
|
+
}
|
|
1225
|
+
if (currentCategory === "display-color") {
|
|
1226
|
+
customHandlers.errorThreshold = buildInputSubmenu("Error Threshold (%)", (value) => parseInteger(value, 0, 100));
|
|
1227
|
+
customHandlers.warningThreshold = buildInputSubmenu("Warning Threshold (%)", (value) => parseInteger(value, 0, 100));
|
|
1228
|
+
customHandlers.successThreshold = buildInputSubmenu("Success Threshold (%)", (value) => parseInteger(value, 0, 100));
|
|
1229
|
+
const usageColorItem = items.find((item) => item.id === "usageColorTargets");
|
|
1230
|
+
if (usageColorItem) {
|
|
1231
|
+
usageColorItem.submenu = buildUsageColorSubmenu();
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
if (currentCategory === "display-bar") {
|
|
1235
|
+
customHandlers.barWidth = buildInputSubmenu("Bar Width", parseBarWidth);
|
|
1236
|
+
customHandlers.barCharacter = buildInputSubmenu(
|
|
1237
|
+
"Bar Character",
|
|
1238
|
+
parseBarCharacter,
|
|
1239
|
+
undefined,
|
|
1240
|
+
"Custom bar character(s), set 1 or 2 (fill/empty)",
|
|
1241
|
+
);
|
|
1242
|
+
}
|
|
1243
|
+
if (currentCategory === "display-provider") {
|
|
1244
|
+
customHandlers.providerLabel = buildInputSubmenu("Provider Label", parseProviderLabel);
|
|
1245
|
+
}
|
|
1246
|
+
if (currentCategory === "display-reset") {
|
|
1247
|
+
customHandlers.resetTimeContainment = buildInputSubmenu(
|
|
1248
|
+
"Reset Timer Containment",
|
|
1249
|
+
parseResetContainment,
|
|
1250
|
+
undefined,
|
|
1251
|
+
"Enter 1-2 characters for left/right wrap (e.g. <>).",
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1254
|
+
if (currentCategory === "display-status") {
|
|
1255
|
+
customHandlers.statusIconPack = buildInputSubmenu(
|
|
1256
|
+
"Custom Status Icons",
|
|
1257
|
+
parseStatusIconCustom,
|
|
1258
|
+
undefined,
|
|
1259
|
+
"Enter four characters in order: OK, warning, error, unknown (e.g. ✓⚠×?). Applied to none, minor/maintenance, major/critical, and unknown statuses.",
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
if (currentCategory === "display-divider") {
|
|
1263
|
+
customHandlers.dividerCharacter = buildInputSubmenu("Divider Character", parseDividerCharacter);
|
|
1264
|
+
customHandlers.dividerBlanks = buildInputSubmenu("Divider Blanks", parseDividerBlanks);
|
|
1265
|
+
}
|
|
1266
|
+
attachCustomInputs(items, customHandlers);
|
|
1267
|
+
|
|
1268
|
+
handleChange = (id, value) => {
|
|
1269
|
+
settings = applyDisplayChange(settings, id, value);
|
|
1270
|
+
saveSettings(settings);
|
|
1271
|
+
if (onSettingsChange) void onSettingsChange(settings);
|
|
1272
|
+
if (currentCategory === "display-layout" && id === "widgetPlacement") {
|
|
1273
|
+
rebuild();
|
|
1274
|
+
tui.requestRender();
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
if (currentCategory === "display-bar" && id === "barType") {
|
|
1278
|
+
rebuild();
|
|
1279
|
+
tui.requestRender();
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
if (currentCategory === "display-status") {
|
|
1283
|
+
if (id === "statusIndicatorMode") {
|
|
1284
|
+
rebuild();
|
|
1285
|
+
tui.requestRender();
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
|
|
1291
|
+
const settingsHintText = "↓ navigate • ←/→ change • Enter/Space edit custom • Esc to cancel";
|
|
1292
|
+
const customTheme = {
|
|
1293
|
+
...getSettingsListTheme(),
|
|
1294
|
+
hint: (text: string) => {
|
|
1295
|
+
if (text.includes("Enter/Space")) {
|
|
1296
|
+
return theme.fg("dim", settingsHintText);
|
|
1297
|
+
}
|
|
1298
|
+
return theme.fg("dim", text);
|
|
1299
|
+
},
|
|
1300
|
+
};
|
|
1301
|
+
const settingsList = new SettingsList(
|
|
1302
|
+
items,
|
|
1303
|
+
Math.min(items.length + 2, 15),
|
|
1304
|
+
customTheme,
|
|
1305
|
+
handleChange,
|
|
1306
|
+
() => {
|
|
1307
|
+
currentCategory = backCategory;
|
|
1308
|
+
rebuild();
|
|
1309
|
+
tui.requestRender();
|
|
1310
|
+
}
|
|
1311
|
+
);
|
|
1312
|
+
activeList = settingsList;
|
|
1313
|
+
container.addChild(settingsList);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
// Help text
|
|
1317
|
+
const usesSettingsList =
|
|
1318
|
+
Boolean(providerCategory) ||
|
|
1319
|
+
currentCategory === "keybindings" ||
|
|
1320
|
+
currentCategory === "display-layout" ||
|
|
1321
|
+
currentCategory === "display-bar" ||
|
|
1322
|
+
currentCategory === "display-provider" ||
|
|
1323
|
+
currentCategory === "display-reset" ||
|
|
1324
|
+
currentCategory === "display-status" ||
|
|
1325
|
+
currentCategory === "display-divider" ||
|
|
1326
|
+
currentCategory === "display-color";
|
|
1327
|
+
if (!usesSettingsList) {
|
|
1328
|
+
let helpText: string;
|
|
1329
|
+
if (
|
|
1330
|
+
currentCategory === "display-theme-save" ||
|
|
1331
|
+
currentCategory === "display-theme-import-name" ||
|
|
1332
|
+
currentCategory === "display-theme-rename"
|
|
1333
|
+
) {
|
|
1334
|
+
helpText = "Type name • Enter to save • Esc back";
|
|
1335
|
+
} else if (currentCategory === "display-theme-import") {
|
|
1336
|
+
helpText = "Paste theme share string • Enter to import • Esc back";
|
|
1337
|
+
} else if (
|
|
1338
|
+
currentCategory === "main" ||
|
|
1339
|
+
currentCategory === "providers" ||
|
|
1340
|
+
currentCategory === "display" ||
|
|
1341
|
+
currentCategory === "display-theme" ||
|
|
1342
|
+
currentCategory === "display-theme-load" ||
|
|
1343
|
+
currentCategory === "display-theme-action" ||
|
|
1344
|
+
currentCategory === "display-theme-random" ||
|
|
1345
|
+
currentCategory === "display-theme-restore"
|
|
1346
|
+
) {
|
|
1347
|
+
helpText = "↑↓ navigate • Enter/Space select • Esc back";
|
|
1348
|
+
} else {
|
|
1349
|
+
helpText = "↑↓ navigate • Enter/Space to change • Esc to cancel";
|
|
1350
|
+
}
|
|
1351
|
+
if (tooltipText) {
|
|
1352
|
+
container.addChild(new Spacer(1));
|
|
1353
|
+
container.addChild(tooltipText);
|
|
1354
|
+
}
|
|
1355
|
+
container.addChild(new Spacer(1));
|
|
1356
|
+
container.addChild(new Text(theme.fg("dim", helpText), 1, 0));
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
// Bottom border
|
|
1360
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
rebuild();
|
|
1364
|
+
|
|
1365
|
+
return {
|
|
1366
|
+
render(width: number) {
|
|
1367
|
+
return container.render(width);
|
|
1368
|
+
},
|
|
1369
|
+
invalidate() {
|
|
1370
|
+
container.invalidate();
|
|
1371
|
+
},
|
|
1372
|
+
handleInput(data: string) {
|
|
1373
|
+
if (data === " ") {
|
|
1374
|
+
if (activeList && "handleInput" in activeList && activeList.handleInput) {
|
|
1375
|
+
activeList.handleInput("\r");
|
|
1376
|
+
}
|
|
1377
|
+
tui.requestRender();
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
if (activeList && "handleInput" in activeList && activeList.handleInput) {
|
|
1381
|
+
activeList.handleInput(data);
|
|
1382
|
+
}
|
|
1383
|
+
tui.requestRender();
|
|
1384
|
+
},
|
|
1385
|
+
};
|
|
1386
|
+
}).then(resolve);
|
|
1387
|
+
});
|
|
1388
|
+
}
|