pi-one-ui 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -1
- package/README.en.md +19 -6
- package/README.md +19 -6
- package/extensions/app/config/context.ts +42 -0
- package/extensions/app/config/editor.ts +80 -0
- package/extensions/app/config/footer.ts +291 -0
- package/extensions/app/config/panel.ts +1 -5
- package/extensions/app/config/renderer.ts +1 -6
- package/extensions/app/config/shell.ts +358 -1435
- package/extensions/app/config/store.ts +22 -1
- package/extensions/app/config/values.ts +33 -0
- package/extensions/app/config/working-line.ts +100 -0
- package/extensions/app/overlay/input-router.ts +56 -32
- package/extensions/app/overlay/overlay-manager.ts +183 -21
- package/extensions/app/runtime/event-coordinator.ts +90 -42
- package/extensions/{features → app/runtime}/flush-docked-bash.ts +12 -2
- package/extensions/app/runtime/render-scheduler.ts +35 -31
- package/extensions/app/runtime/tui-runtime.ts +315 -312
- package/extensions/app/settings/controller.ts +202 -0
- package/extensions/app/settings/panel.ts +81 -0
- package/extensions/app/settings/presets.ts +43 -0
- package/extensions/features/context-inspector/index.ts +83 -662
- package/extensions/layouts/context/index.ts +58 -161
- package/extensions/layouts/context/message/user-message.ts +1 -1
- package/extensions/layouts/context/renderer/index.ts +31 -13
- package/extensions/layouts/context/renderer/mouse/interaction.ts +29 -14
- package/extensions/layouts/editor/controller.ts +9 -18
- package/extensions/layouts/editor/factory.ts +3 -3
- package/extensions/layouts/footer/controller.ts +30 -19
- package/extensions/layouts/footer/footer.ts +2 -2
- package/extensions/layouts/overlay/context-inspector.ts +497 -0
- package/extensions/{app → layouts}/overlay/selector-border.ts +5 -5
- package/extensions/{app → layouts}/overlay/selector-controller.ts +1 -1
- package/extensions/layouts/overlay/settings-panel.ts +359 -0
- package/extensions/{app/commands → layouts/overlay}/settings-previews.ts +4 -4
- package/extensions/layouts/working-line/controller.ts +3 -29
- package/extensions/layouts/working-line/working-line.ts +17 -114
- package/extensions/shared/working-line-text.ts +108 -0
- package/package.json +1 -1
- package/extensions/app/host/pi-extension-port.ts +0 -25
- package/extensions/app/host/pi-ui-port.ts +0 -52
- package/extensions/app/host/tui-capabilities.ts +0 -27
- package/extensions/app/ownership/layout-registry.ts +0 -65
- package/extensions/app/ownership.ts +0 -32
- package/extensions/app/panel.ts +0 -770
- package/extensions/app/presets.ts +0 -35
- package/extensions/app/runtime/runtime-state.ts +0 -83
- /package/extensions/{layouts/working-line → app/config}/working-line-messages.ts +0 -0
- /package/extensions/app/{ownership → runtime}/prototype-patch-registry.ts +0 -0
package/extensions/app/panel.ts
DELETED
|
@@ -1,770 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type ExtensionCommandContext,
|
|
3
|
-
type ExtensionContext,
|
|
4
|
-
type ExtensionUIContext,
|
|
5
|
-
getSettingsListTheme,
|
|
6
|
-
} from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import {
|
|
8
|
-
type Component,
|
|
9
|
-
matchesKey,
|
|
10
|
-
type OverlayHandle,
|
|
11
|
-
type SettingItem,
|
|
12
|
-
SettingsList,
|
|
13
|
-
truncateToWidth,
|
|
14
|
-
} from "@earendil-works/pi-tui";
|
|
15
|
-
import {
|
|
16
|
-
renderEditorSettingsPreview,
|
|
17
|
-
renderUserMessageSettingsPreview,
|
|
18
|
-
} from "./commands/settings-previews.ts";
|
|
19
|
-
import { loadPanelOverlayConfig } from "./config/panel.ts";
|
|
20
|
-
import {
|
|
21
|
-
type CompactStyleMode,
|
|
22
|
-
type Config as ContextConfig,
|
|
23
|
-
DIFF_INDICATOR_MODES,
|
|
24
|
-
DIFF_VIEW_MODES,
|
|
25
|
-
config as rendererConfig,
|
|
26
|
-
updateConfig as updateContextConfig,
|
|
27
|
-
} from "./config/renderer.ts";
|
|
28
|
-
import {
|
|
29
|
-
type EditorComponentConfig,
|
|
30
|
-
type FooterComponentConfig,
|
|
31
|
-
type FooterStyle,
|
|
32
|
-
loadConfig as loadShellConfig,
|
|
33
|
-
saveEditorBorderColorMode,
|
|
34
|
-
saveEditorComponentPatch,
|
|
35
|
-
saveFooterComponentPatch,
|
|
36
|
-
saveUserMessagesComponentPatch,
|
|
37
|
-
saveWorkingLineComponentPatch,
|
|
38
|
-
type UserMessageStyle,
|
|
39
|
-
type UserMessagesComponentConfig,
|
|
40
|
-
type WorkingLineComponentPatch,
|
|
41
|
-
type WorkingLineSpinner,
|
|
42
|
-
} from "./config/shell.ts";
|
|
43
|
-
import { overlayManager } from "./overlay/overlay-manager.ts";
|
|
44
|
-
import { ownerFor } from "./ownership.ts";
|
|
45
|
-
import { applyPreset, PRESET_VALUES, type Preset } from "./presets.ts";
|
|
46
|
-
|
|
47
|
-
const MESSAGE_STYLES: UserMessageStyle[] = [
|
|
48
|
-
"framed",
|
|
49
|
-
"framed-copy-friendly",
|
|
50
|
-
"compact",
|
|
51
|
-
"labeled",
|
|
52
|
-
];
|
|
53
|
-
const FOOTER_STYLES: FooterStyle[] = ["starship", "native", "hidden"];
|
|
54
|
-
const WORKING_SPINNERS: WorkingLineSpinner[] = [
|
|
55
|
-
"braille",
|
|
56
|
-
"star-bloom",
|
|
57
|
-
"pinwheel",
|
|
58
|
-
"claude-inspired",
|
|
59
|
-
"pulse",
|
|
60
|
-
];
|
|
61
|
-
const PREVIEW_LINES = ["0", "1", "3", "5", "10"];
|
|
62
|
-
|
|
63
|
-
const SECTIONS = [
|
|
64
|
-
{ id: "header", label: "Header" },
|
|
65
|
-
{ id: "context", label: "Context" },
|
|
66
|
-
{ id: "workingLine", label: "WorkingLine" },
|
|
67
|
-
{ id: "editor", label: "Editor" },
|
|
68
|
-
{ id: "footer", label: "Footer" },
|
|
69
|
-
{ id: "features", label: "Features" },
|
|
70
|
-
{ id: "presets", label: "Presets" },
|
|
71
|
-
] as const;
|
|
72
|
-
type SectionId = (typeof SECTIONS)[number]["id"];
|
|
73
|
-
|
|
74
|
-
function onOff(value: boolean): string {
|
|
75
|
-
return value ? "on" : "off";
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function renderTabs(theme: any, active: number, width: number): string {
|
|
79
|
-
const text = SECTIONS.map((section, index) => {
|
|
80
|
-
const label = index === active ? theme.bold(section.label) : section.label;
|
|
81
|
-
return index === active ? theme.fg("text", label) : theme.fg("dim", label);
|
|
82
|
-
}).join(theme.fg("dim", " / "));
|
|
83
|
-
return truncateToWidth(text, Math.max(0, width));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** Adds a stable chrome frame around the settings content. */
|
|
87
|
-
function renderPanelFrame(theme: any, width: number, rows: string[]): string[] {
|
|
88
|
-
const safeWidth = Math.max(1, width);
|
|
89
|
-
const border = (text: string) => theme.fg("border", text);
|
|
90
|
-
if (safeWidth === 1)
|
|
91
|
-
return [border("│"), ...rows.map(() => border("│")), border("│")];
|
|
92
|
-
|
|
93
|
-
const innerWidth = safeWidth - 2;
|
|
94
|
-
const content = rows.map(
|
|
95
|
-
(row) =>
|
|
96
|
-
`${border("│")}${truncateToWidth(row, innerWidth, "", true)}${border("│")}`,
|
|
97
|
-
);
|
|
98
|
-
if (safeWidth === 2) return [border("╭╮"), ...content, border("╰╯")];
|
|
99
|
-
return [
|
|
100
|
-
border(`╭${"─".repeat(innerWidth)}╮`),
|
|
101
|
-
...content,
|
|
102
|
-
border(`╰${"─".repeat(innerWidth)}╯`),
|
|
103
|
-
];
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function sectionDescription(section: SectionId): string {
|
|
107
|
-
switch (section) {
|
|
108
|
-
case "header":
|
|
109
|
-
return "Header owns startup branding and startup guidance.";
|
|
110
|
-
case "context":
|
|
111
|
-
return `${ownerFor("toolRenderer")} owns messages, tool cards, diffs, thinking, and summaries.`;
|
|
112
|
-
case "workingLine":
|
|
113
|
-
return "WorkingLine is the sole owner of Pi's unkeyed working row.";
|
|
114
|
-
case "editor":
|
|
115
|
-
return `${ownerFor("editor")} owns the input editor factory and its styles.`;
|
|
116
|
-
case "footer":
|
|
117
|
-
return `${ownerFor("footer")} owns footer rendering and project status segments.`;
|
|
118
|
-
case "features":
|
|
119
|
-
return "Feature switches are saved immediately and applied after /reload.";
|
|
120
|
-
case "presets":
|
|
121
|
-
return "Presets change ownership defaults while preserving detailed options.";
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Builds settings owned by the Editor layout.
|
|
127
|
-
*/
|
|
128
|
-
function editorItems(): SettingItem[] {
|
|
129
|
-
const config = loadShellConfig();
|
|
130
|
-
return [
|
|
131
|
-
{
|
|
132
|
-
id: "editorStyle",
|
|
133
|
-
label: "Editor",
|
|
134
|
-
description:
|
|
135
|
-
"on applies the Minimalist decoration; off restores Pi's native editor.",
|
|
136
|
-
currentValue: config.components.editor.style,
|
|
137
|
-
values: ["on", "off"],
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
id: "editorBorderColorMode",
|
|
141
|
-
label: "Border color",
|
|
142
|
-
description:
|
|
143
|
-
"static keeps one border color; adaptive shifts the border with the effort level.",
|
|
144
|
-
currentValue: config.components.editor.borderColorMode,
|
|
145
|
-
values: ["static", "adaptive"],
|
|
146
|
-
},
|
|
147
|
-
];
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Builds settings owned by the WorkingLine layout.
|
|
152
|
-
*/
|
|
153
|
-
function workingLineItems(): SettingItem[] {
|
|
154
|
-
const config = loadShellConfig();
|
|
155
|
-
return [
|
|
156
|
-
{
|
|
157
|
-
id: "workingLineEnabled",
|
|
158
|
-
label: "Working line",
|
|
159
|
-
description: "Enable the sole owner of Pi's working row.",
|
|
160
|
-
currentValue: onOff(config.components.workingLine.enabled),
|
|
161
|
-
values: ["on", "off"],
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
id: "workingLineSpinner",
|
|
165
|
-
label: "Working spinner",
|
|
166
|
-
description: "Select the working line animation preset.",
|
|
167
|
-
currentValue: config.components.workingLine.spinner,
|
|
168
|
-
values: WORKING_SPINNERS,
|
|
169
|
-
},
|
|
170
|
-
];
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Builds settings owned by the Footer layout.
|
|
175
|
-
*/
|
|
176
|
-
function footerItems(): SettingItem[] {
|
|
177
|
-
const config = loadShellConfig();
|
|
178
|
-
return [
|
|
179
|
-
{
|
|
180
|
-
id: "footerStyle",
|
|
181
|
-
label: "Footer",
|
|
182
|
-
description: "Choose Starship, native, or hidden footer rendering.",
|
|
183
|
-
currentValue: config.components.footer.style,
|
|
184
|
-
values: FOOTER_STYLES,
|
|
185
|
-
},
|
|
186
|
-
];
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Builds settings owned by the Context layout.
|
|
191
|
-
*/
|
|
192
|
-
function contextItems(config: ContextConfig): SettingItem[] {
|
|
193
|
-
const shellConfig = loadShellConfig();
|
|
194
|
-
return [
|
|
195
|
-
{
|
|
196
|
-
id: "userMessagesEnabled",
|
|
197
|
-
label: "User messages",
|
|
198
|
-
description: "Enable custom rendering for previous user messages.",
|
|
199
|
-
currentValue: onOff(shellConfig.components.userMessages.enabled),
|
|
200
|
-
values: ["on", "off"],
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
id: "userMessagesStyle",
|
|
204
|
-
label: "Message style",
|
|
205
|
-
description: "Choose the previous user message treatment.",
|
|
206
|
-
currentValue: shellConfig.components.userMessages.style,
|
|
207
|
-
values: MESSAGE_STYLES,
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
id: "contextMode",
|
|
211
|
-
label: "Tool style",
|
|
212
|
-
description:
|
|
213
|
-
"on = rich cards, compact = one summary per assistant message, off = native.",
|
|
214
|
-
currentValue: config.mode,
|
|
215
|
-
values: ["on", "compact", "off"],
|
|
216
|
-
},
|
|
217
|
-
{
|
|
218
|
-
id: "diffViewMode",
|
|
219
|
-
label: "Diff layout",
|
|
220
|
-
description: "Choose automatic, side-by-side, or unified diff layout.",
|
|
221
|
-
currentValue: config.diffViewMode,
|
|
222
|
-
values: [...DIFF_VIEW_MODES],
|
|
223
|
-
},
|
|
224
|
-
{
|
|
225
|
-
id: "diffIndicatorMode",
|
|
226
|
-
label: "Diff indicator",
|
|
227
|
-
description: "Choose bars, classic +/- gutters, or no indicator.",
|
|
228
|
-
currentValue: config.diffIndicatorMode,
|
|
229
|
-
values: [...DIFF_INDICATOR_MODES],
|
|
230
|
-
},
|
|
231
|
-
{
|
|
232
|
-
id: "diffWordWrap",
|
|
233
|
-
label: "Diff word wrap",
|
|
234
|
-
description: "Wrap long diff lines to the available terminal width.",
|
|
235
|
-
currentValue: onOff(config.diffWordWrap),
|
|
236
|
-
values: ["on", "off"],
|
|
237
|
-
},
|
|
238
|
-
{
|
|
239
|
-
id: "thinkingPreviewLines",
|
|
240
|
-
label: "Thinking preview",
|
|
241
|
-
description: "Number of thinking body lines shown before expansion.",
|
|
242
|
-
currentValue: String(config.previewLines),
|
|
243
|
-
values: PREVIEW_LINES,
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
id: "dimThinkingText",
|
|
247
|
-
label: "Dim thinking text",
|
|
248
|
-
description: "Use the dim theme color for thinking body text.",
|
|
249
|
-
currentValue: onOff(config.dimThinkingText),
|
|
250
|
-
values: ["on", "off"],
|
|
251
|
-
},
|
|
252
|
-
];
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Builds settings owned by the Header layout.
|
|
257
|
-
*/
|
|
258
|
-
function headerItems(config: ContextConfig): SettingItem[] {
|
|
259
|
-
return [
|
|
260
|
-
{
|
|
261
|
-
id: "showStartupHeader",
|
|
262
|
-
label: "Startup header",
|
|
263
|
-
description: "Show the custom startup header on new sessions.",
|
|
264
|
-
currentValue: onOff(config.showStartupHeader),
|
|
265
|
-
values: ["on", "off"],
|
|
266
|
-
},
|
|
267
|
-
];
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Builds non-visual feature switches.
|
|
272
|
-
*/
|
|
273
|
-
function featureItems(config: ContextConfig): SettingItem[] {
|
|
274
|
-
return [
|
|
275
|
-
{
|
|
276
|
-
id: "enableSessionReference",
|
|
277
|
-
label: "Session reference",
|
|
278
|
-
description: "Enable @ session search and context injection.",
|
|
279
|
-
currentValue: onOff(config.enableSessionReference),
|
|
280
|
-
values: ["on", "off"],
|
|
281
|
-
},
|
|
282
|
-
{
|
|
283
|
-
id: "enableSubagentAutocomplete",
|
|
284
|
-
label: "Subagent autocomplete",
|
|
285
|
-
description: "Enable @ subagent completion and delegation hints.",
|
|
286
|
-
currentValue: onOff(config.enableSubagentAutocomplete),
|
|
287
|
-
values: ["on", "off"],
|
|
288
|
-
},
|
|
289
|
-
{
|
|
290
|
-
id: "enableContextCommand",
|
|
291
|
-
label: "Context command",
|
|
292
|
-
description: "Keep the /context inspection command enabled.",
|
|
293
|
-
currentValue: onOff(config.enableContextCommand),
|
|
294
|
-
values: ["on", "off"],
|
|
295
|
-
},
|
|
296
|
-
{
|
|
297
|
-
id: "enableAgentSummary",
|
|
298
|
-
label: "Agent summary",
|
|
299
|
-
description: "Show per-turn tool statistics after an agent turn.",
|
|
300
|
-
currentValue: onOff(config.enableAgentSummary),
|
|
301
|
-
values: ["on", "off"],
|
|
302
|
-
},
|
|
303
|
-
{
|
|
304
|
-
id: "enableAliases",
|
|
305
|
-
label: "Command aliases",
|
|
306
|
-
description: "Register /clear and /exit aliases.",
|
|
307
|
-
currentValue: onOff(config.enableAliases),
|
|
308
|
-
values: ["on", "off"],
|
|
309
|
-
},
|
|
310
|
-
{
|
|
311
|
-
id: "enableEffortCommand",
|
|
312
|
-
label: "Effort command",
|
|
313
|
-
description: "Register the /effort thinking-level command.",
|
|
314
|
-
currentValue: onOff(config.enableEffortCommand),
|
|
315
|
-
values: ["on", "off"],
|
|
316
|
-
},
|
|
317
|
-
];
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function presetItems(): SettingItem[] {
|
|
321
|
-
return PRESET_VALUES.map((preset) => ({
|
|
322
|
-
id: `preset:${preset}`,
|
|
323
|
-
label: preset,
|
|
324
|
-
description:
|
|
325
|
-
preset === "balanced"
|
|
326
|
-
? "Zentui shell + rich CC Style context renderer."
|
|
327
|
-
: preset === "compact"
|
|
328
|
-
? "Zentui shell + compact CC Style context."
|
|
329
|
-
: "Disable custom shell and context rendering.",
|
|
330
|
-
currentValue: "apply",
|
|
331
|
-
values: ["apply"],
|
|
332
|
-
}));
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function itemsFor(section: SectionId): SettingItem[] {
|
|
336
|
-
switch (section) {
|
|
337
|
-
case "header":
|
|
338
|
-
return headerItems(rendererConfig);
|
|
339
|
-
case "context":
|
|
340
|
-
return contextItems(rendererConfig);
|
|
341
|
-
case "workingLine":
|
|
342
|
-
return workingLineItems();
|
|
343
|
-
case "editor":
|
|
344
|
-
return editorItems();
|
|
345
|
-
case "footer":
|
|
346
|
-
return footerItems();
|
|
347
|
-
case "features":
|
|
348
|
-
return featureItems(rendererConfig);
|
|
349
|
-
case "presets":
|
|
350
|
-
return presetItems();
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function isOn(value: string): boolean {
|
|
355
|
-
return value === "on";
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
export type TuiPanelRuntime = {
|
|
359
|
-
setEditorComponent: (
|
|
360
|
-
patch: Partial<EditorComponentConfig>,
|
|
361
|
-
ctx: ExtensionContext,
|
|
362
|
-
) => { applied: boolean; reason?: string };
|
|
363
|
-
setUserMessagesComponent: (
|
|
364
|
-
patch: Partial<UserMessagesComponentConfig>,
|
|
365
|
-
ctx: ExtensionContext,
|
|
366
|
-
) => void;
|
|
367
|
-
setWorkingLineComponent: (
|
|
368
|
-
patch: WorkingLineComponentPatch,
|
|
369
|
-
ctx: ExtensionContext,
|
|
370
|
-
) => { applied: boolean; reason?: string };
|
|
371
|
-
setFooterComponent: (
|
|
372
|
-
patch: Partial<FooterComponentConfig>,
|
|
373
|
-
ctx: ExtensionContext,
|
|
374
|
-
) => void;
|
|
375
|
-
setContextMode: (mode: CompactStyleMode, ctx: ExtensionContext) => void;
|
|
376
|
-
updateContextConfig: (
|
|
377
|
-
patch: Partial<ContextConfig>,
|
|
378
|
-
ctx: ExtensionContext,
|
|
379
|
-
) => void;
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
type UnifiedPanelDeps = {
|
|
383
|
-
runtime?: TuiPanelRuntime;
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Renders the Editor layout preview for the settings panel.
|
|
388
|
-
*/
|
|
389
|
-
function renderEditorPreview(theme: any, width: number): string[] {
|
|
390
|
-
const config = loadShellConfig();
|
|
391
|
-
const previewWidth = Math.max(20, Math.min(72, width - 2));
|
|
392
|
-
return [
|
|
393
|
-
"",
|
|
394
|
-
theme.fg("muted", " Editor preview"),
|
|
395
|
-
...renderEditorSettingsPreview(config, theme, previewWidth).map(
|
|
396
|
-
(line) => ` ${line}`,
|
|
397
|
-
),
|
|
398
|
-
];
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Renders the Context layout User Message preview for the settings panel.
|
|
403
|
-
*/
|
|
404
|
-
function renderContextPreview(theme: any, width: number): string[] {
|
|
405
|
-
const config = loadShellConfig();
|
|
406
|
-
const previewWidth = Math.max(20, Math.min(72, width - 2));
|
|
407
|
-
return [
|
|
408
|
-
"",
|
|
409
|
-
theme.fg("muted", " User message preview"),
|
|
410
|
-
...renderUserMessageSettingsPreview(config, theme, previewWidth).map(
|
|
411
|
-
(line) => ` ${line}`,
|
|
412
|
-
),
|
|
413
|
-
];
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function saveNotice(ctx: any, label: string, value: string): void {
|
|
417
|
-
ctx.ui.notify(
|
|
418
|
-
`${label}: ${value} saved; live preview updated when supported.`,
|
|
419
|
-
"info",
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function notifyUpdateError(ctx: any, error: unknown): void {
|
|
424
|
-
ctx.ui?.notify?.(
|
|
425
|
-
`Could not update /oneui setting: ${error instanceof Error ? error.message : String(error)}`,
|
|
426
|
-
"error",
|
|
427
|
-
);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/**
|
|
431
|
-
* Persists one panel setting and reconciles its owning runtime layout.
|
|
432
|
-
*
|
|
433
|
-
* @param id Stable settings item identifier.
|
|
434
|
-
* @param value Selected settings value.
|
|
435
|
-
* @param ctx Active Pi extension context.
|
|
436
|
-
* @param deps Live runtime controllers available to the panel.
|
|
437
|
-
*/
|
|
438
|
-
function updateSetting(
|
|
439
|
-
id: string,
|
|
440
|
-
value: string,
|
|
441
|
-
ctx: any,
|
|
442
|
-
deps: UnifiedPanelDeps,
|
|
443
|
-
): void {
|
|
444
|
-
if (id === "editorStyle" && (value === "on" || value === "off")) {
|
|
445
|
-
if (deps.runtime) deps.runtime.setEditorComponent({ style: value }, ctx);
|
|
446
|
-
else saveEditorComponentPatch({ style: value });
|
|
447
|
-
saveNotice(ctx, "Editor", value);
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
|
-
if (
|
|
451
|
-
id === "editorBorderColorMode" &&
|
|
452
|
-
(value === "static" || value === "adaptive")
|
|
453
|
-
) {
|
|
454
|
-
if (deps.runtime)
|
|
455
|
-
deps.runtime.setEditorComponent({ borderColorMode: value }, ctx);
|
|
456
|
-
else saveEditorBorderColorMode(value);
|
|
457
|
-
saveNotice(ctx, "Border color", value);
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
460
|
-
if (id === "userMessagesEnabled") {
|
|
461
|
-
if (deps.runtime)
|
|
462
|
-
deps.runtime.setUserMessagesComponent({ enabled: isOn(value) }, ctx);
|
|
463
|
-
else saveUserMessagesComponentPatch({ enabled: isOn(value) });
|
|
464
|
-
saveNotice(ctx, "User messages", value);
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
if (
|
|
468
|
-
id === "userMessagesStyle" &&
|
|
469
|
-
MESSAGE_STYLES.includes(value as UserMessageStyle)
|
|
470
|
-
) {
|
|
471
|
-
if (deps.runtime)
|
|
472
|
-
deps.runtime.setUserMessagesComponent(
|
|
473
|
-
{ style: value as UserMessageStyle },
|
|
474
|
-
ctx,
|
|
475
|
-
);
|
|
476
|
-
else saveUserMessagesComponentPatch({ style: value as UserMessageStyle });
|
|
477
|
-
saveNotice(ctx, "Message style", value);
|
|
478
|
-
return;
|
|
479
|
-
}
|
|
480
|
-
if (id === "workingLineEnabled") {
|
|
481
|
-
if (deps.runtime)
|
|
482
|
-
deps.runtime.setWorkingLineComponent({ enabled: isOn(value) }, ctx);
|
|
483
|
-
else saveWorkingLineComponentPatch({ enabled: isOn(value) });
|
|
484
|
-
saveNotice(ctx, "Working line", value);
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
if (
|
|
488
|
-
id === "workingLineSpinner" &&
|
|
489
|
-
WORKING_SPINNERS.includes(value as WorkingLineSpinner)
|
|
490
|
-
) {
|
|
491
|
-
if (deps.runtime)
|
|
492
|
-
deps.runtime.setWorkingLineComponent(
|
|
493
|
-
{ spinner: value as WorkingLineSpinner },
|
|
494
|
-
ctx,
|
|
495
|
-
);
|
|
496
|
-
else
|
|
497
|
-
saveWorkingLineComponentPatch({ spinner: value as WorkingLineSpinner });
|
|
498
|
-
saveNotice(ctx, "Working spinner", value);
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
if (id === "footerStyle" && FOOTER_STYLES.includes(value as FooterStyle)) {
|
|
502
|
-
if (deps.runtime)
|
|
503
|
-
deps.runtime.setFooterComponent({ style: value as FooterStyle }, ctx);
|
|
504
|
-
else saveFooterComponentPatch({ style: value as FooterStyle });
|
|
505
|
-
saveNotice(ctx, "Footer", value);
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
if (id === "contextMode") {
|
|
509
|
-
if (deps.runtime)
|
|
510
|
-
deps.runtime.setContextMode(value as CompactStyleMode, ctx);
|
|
511
|
-
else updateContextConfig({ mode: value as CompactStyleMode });
|
|
512
|
-
saveNotice(ctx, "Tool style", value);
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
if (id === "diffViewMode" && DIFF_VIEW_MODES.includes(value as never)) {
|
|
516
|
-
const patch = { diffViewMode: value as ContextConfig["diffViewMode"] };
|
|
517
|
-
if (deps.runtime) deps.runtime.updateContextConfig(patch, ctx);
|
|
518
|
-
else updateContextConfig(patch);
|
|
519
|
-
saveNotice(ctx, "Diff layout", value);
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
522
|
-
if (
|
|
523
|
-
id === "diffIndicatorMode" &&
|
|
524
|
-
DIFF_INDICATOR_MODES.includes(value as never)
|
|
525
|
-
) {
|
|
526
|
-
const patch = {
|
|
527
|
-
diffIndicatorMode: value as ContextConfig["diffIndicatorMode"],
|
|
528
|
-
};
|
|
529
|
-
if (deps.runtime) deps.runtime.updateContextConfig(patch, ctx);
|
|
530
|
-
else updateContextConfig(patch);
|
|
531
|
-
saveNotice(ctx, "Diff indicator", value);
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
534
|
-
if (id === "diffWordWrap") {
|
|
535
|
-
const patch = { diffWordWrap: isOn(value) };
|
|
536
|
-
if (deps.runtime) deps.runtime.updateContextConfig(patch, ctx);
|
|
537
|
-
else updateContextConfig(patch);
|
|
538
|
-
saveNotice(ctx, "Diff word wrap", value);
|
|
539
|
-
return;
|
|
540
|
-
}
|
|
541
|
-
if (id === "thinkingPreviewLines") {
|
|
542
|
-
const patch = { previewLines: Number(value) };
|
|
543
|
-
if (deps.runtime) deps.runtime.updateContextConfig(patch, ctx);
|
|
544
|
-
else updateContextConfig(patch);
|
|
545
|
-
saveNotice(ctx, "Thinking preview", value);
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
if (id === "dimThinkingText") {
|
|
549
|
-
const patch = { dimThinkingText: isOn(value) };
|
|
550
|
-
if (deps.runtime) deps.runtime.updateContextConfig(patch, ctx);
|
|
551
|
-
else updateContextConfig(patch);
|
|
552
|
-
saveNotice(ctx, "Thinking text", value);
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
if (
|
|
556
|
-
id === "enableSessionReference" ||
|
|
557
|
-
id === "enableSubagentAutocomplete" ||
|
|
558
|
-
id === "enableContextCommand" ||
|
|
559
|
-
id === "enableAgentSummary" ||
|
|
560
|
-
id === "enableAliases" ||
|
|
561
|
-
id === "enableEffortCommand" ||
|
|
562
|
-
id === "showStartupHeader"
|
|
563
|
-
) {
|
|
564
|
-
const patch = { [id]: isOn(value) } as Partial<ContextConfig>;
|
|
565
|
-
if (deps.runtime) deps.runtime.updateContextConfig(patch, ctx);
|
|
566
|
-
else updateContextConfig(patch);
|
|
567
|
-
saveNotice(ctx, id, value);
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
if (id.startsWith("preset:")) {
|
|
571
|
-
const preset = id.slice("preset:".length);
|
|
572
|
-
if (PRESET_VALUES.includes(preset as Preset)) {
|
|
573
|
-
applyPreset(preset as Preset);
|
|
574
|
-
if (deps.runtime) {
|
|
575
|
-
deps.runtime.setContextMode(
|
|
576
|
-
preset === "compact" ? "compact" : preset === "native" ? "off" : "on",
|
|
577
|
-
ctx,
|
|
578
|
-
);
|
|
579
|
-
}
|
|
580
|
-
saveNotice(ctx, "Preset", preset);
|
|
581
|
-
}
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
let unifiedPanelOpen = false;
|
|
587
|
-
|
|
588
|
-
/**
|
|
589
|
-
* Opens the unified settings panel as a managed overlay.
|
|
590
|
-
*
|
|
591
|
-
* @param ctx Active TUI extension context.
|
|
592
|
-
* @param deps Runtime controllers used for live updates.
|
|
593
|
-
*/
|
|
594
|
-
export async function showOneUiPanel(
|
|
595
|
-
ctx: ExtensionCommandContext,
|
|
596
|
-
deps: UnifiedPanelDeps = {},
|
|
597
|
-
): Promise<void> {
|
|
598
|
-
if (
|
|
599
|
-
ctx?.mode !== "tui" ||
|
|
600
|
-
!ctx?.hasUI ||
|
|
601
|
-
typeof ctx.ui?.custom !== "function"
|
|
602
|
-
) {
|
|
603
|
-
ctx.ui?.notify?.("/oneui requires TUI mode", "warning");
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
if (unifiedPanelOpen) {
|
|
607
|
-
ctx.ui.notify("/oneui panel is already open", "info");
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
unifiedPanelOpen = true;
|
|
612
|
-
try {
|
|
613
|
-
// Read fresh on every open so pi-one-ui.json edits apply without /reload.
|
|
614
|
-
const panelOverlay = loadPanelOverlayConfig();
|
|
615
|
-
let panelHandle: Pick<OverlayHandle, "focus" | "unfocus"> | undefined;
|
|
616
|
-
await overlayManager.run(() =>
|
|
617
|
-
ctx.ui.custom(
|
|
618
|
-
(tui, theme, _keybindings, done) => {
|
|
619
|
-
// Pi's concrete renderers expose focus inspection beyond the narrow TUI port.
|
|
620
|
-
const focusTui = tui as typeof tui & {
|
|
621
|
-
getFocusedComponent?: () => Component | null;
|
|
622
|
-
isOverlayFocused?: () => boolean;
|
|
623
|
-
};
|
|
624
|
-
const originalFocus = focusTui.getFocusedComponent?.();
|
|
625
|
-
let editorFocus:
|
|
626
|
-
| {
|
|
627
|
-
component: Component;
|
|
628
|
-
factory: ReturnType<ExtensionUIContext["getEditorComponent"]>;
|
|
629
|
-
}
|
|
630
|
-
| undefined;
|
|
631
|
-
let activeIndex = 0;
|
|
632
|
-
let list: SettingsList;
|
|
633
|
-
const createList = () => {
|
|
634
|
-
list = new SettingsList(
|
|
635
|
-
itemsFor(SECTIONS[activeIndex].id),
|
|
636
|
-
10,
|
|
637
|
-
getSettingsListTheme(),
|
|
638
|
-
(id, value) => {
|
|
639
|
-
const updatesEditor =
|
|
640
|
-
id === "editorStyle" || id === "editorBorderColorMode";
|
|
641
|
-
if (updatesEditor && originalFocus !== undefined) {
|
|
642
|
-
// Suspend automatic overlay focus restoration while Pi may replace
|
|
643
|
-
// and roll back an editor, keeping the final instance observable.
|
|
644
|
-
panelHandle?.unfocus?.({
|
|
645
|
-
target: editorFocus?.component ?? originalFocus,
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
const previousFocus = focusTui.getFocusedComponent?.();
|
|
649
|
-
try {
|
|
650
|
-
updateSetting(id, value, ctx, deps);
|
|
651
|
-
list.updateValue(id, value);
|
|
652
|
-
} catch (error) {
|
|
653
|
-
notifyUpdateError(ctx, error);
|
|
654
|
-
createList();
|
|
655
|
-
list.selectItem(id);
|
|
656
|
-
} finally {
|
|
657
|
-
if (updatesEditor) {
|
|
658
|
-
// Replacements (including rollback) focus the new instance, but the
|
|
659
|
-
// overlay still retains its old preFocus. Capture before refocusing.
|
|
660
|
-
const nextFocus = focusTui.getFocusedComponent?.();
|
|
661
|
-
if (
|
|
662
|
-
nextFocus &&
|
|
663
|
-
nextFocus !== previousFocus &&
|
|
664
|
-
!focusTui.isOverlayFocused?.()
|
|
665
|
-
) {
|
|
666
|
-
try {
|
|
667
|
-
editorFocus = {
|
|
668
|
-
component: nextFocus,
|
|
669
|
-
factory: ctx.ui.getEditorComponent(),
|
|
670
|
-
};
|
|
671
|
-
} catch {
|
|
672
|
-
editorFocus = undefined;
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
panelHandle?.focus();
|
|
676
|
-
}
|
|
677
|
-
tui.requestRender();
|
|
678
|
-
}
|
|
679
|
-
},
|
|
680
|
-
() => {
|
|
681
|
-
done(undefined);
|
|
682
|
-
// Only correct this panel's stale return target; other overlays and
|
|
683
|
-
// later editor owners keep their focus. Stale session contexts may throw.
|
|
684
|
-
if (
|
|
685
|
-
!editorFocus ||
|
|
686
|
-
tui.hasOverlay() ||
|
|
687
|
-
focusTui.getFocusedComponent?.() !== originalFocus
|
|
688
|
-
) {
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
try {
|
|
692
|
-
if (ctx.ui.getEditorComponent() !== editorFocus.factory) {
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
} catch {
|
|
696
|
-
return;
|
|
697
|
-
}
|
|
698
|
-
tui.setFocus(editorFocus.component);
|
|
699
|
-
tui.requestRender();
|
|
700
|
-
},
|
|
701
|
-
);
|
|
702
|
-
};
|
|
703
|
-
createList();
|
|
704
|
-
|
|
705
|
-
const switchSection = (delta: number) => {
|
|
706
|
-
activeIndex =
|
|
707
|
-
(activeIndex + delta + SECTIONS.length) % SECTIONS.length;
|
|
708
|
-
createList();
|
|
709
|
-
tui.requestRender();
|
|
710
|
-
};
|
|
711
|
-
|
|
712
|
-
return {
|
|
713
|
-
render: (width: number) => {
|
|
714
|
-
const contentWidth = Math.max(0, width - 2);
|
|
715
|
-
return renderPanelFrame(theme, width, [
|
|
716
|
-
renderTabs(theme, activeIndex, contentWidth),
|
|
717
|
-
theme.fg("dim", "─".repeat(contentWidth)),
|
|
718
|
-
theme.fg(
|
|
719
|
-
"muted",
|
|
720
|
-
` ${sectionDescription(SECTIONS[activeIndex].id)}`,
|
|
721
|
-
),
|
|
722
|
-
"",
|
|
723
|
-
...list.render(contentWidth),
|
|
724
|
-
...(SECTIONS[activeIndex].id === "editor"
|
|
725
|
-
? renderEditorPreview(theme, contentWidth)
|
|
726
|
-
: SECTIONS[activeIndex].id === "context"
|
|
727
|
-
? renderContextPreview(theme, contentWidth)
|
|
728
|
-
: []),
|
|
729
|
-
"",
|
|
730
|
-
truncateToWidth(
|
|
731
|
-
theme.fg(
|
|
732
|
-
"dim",
|
|
733
|
-
" Tab/Shift+Tab switch sections · Enter/Space change · Esc close",
|
|
734
|
-
),
|
|
735
|
-
contentWidth,
|
|
736
|
-
),
|
|
737
|
-
]);
|
|
738
|
-
},
|
|
739
|
-
invalidate: () => list.invalidate(),
|
|
740
|
-
handleInput: (data: string) => {
|
|
741
|
-
if (data === "\x1b[Z" || matchesKey(data, "shift+tab")) {
|
|
742
|
-
switchSection(-1);
|
|
743
|
-
return;
|
|
744
|
-
}
|
|
745
|
-
if (matchesKey(data, "tab")) {
|
|
746
|
-
switchSection(1);
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
list.handleInput(data);
|
|
750
|
-
},
|
|
751
|
-
};
|
|
752
|
-
},
|
|
753
|
-
{
|
|
754
|
-
overlay: true,
|
|
755
|
-
overlayOptions: {
|
|
756
|
-
anchor: panelOverlay.anchor,
|
|
757
|
-
width: panelOverlay.width,
|
|
758
|
-
maxHeight: panelOverlay.maxHeight,
|
|
759
|
-
margin: panelOverlay.margin,
|
|
760
|
-
},
|
|
761
|
-
onHandle: (handle: OverlayHandle) => {
|
|
762
|
-
panelHandle = handle;
|
|
763
|
-
},
|
|
764
|
-
},
|
|
765
|
-
),
|
|
766
|
-
);
|
|
767
|
-
} finally {
|
|
768
|
-
unifiedPanelOpen = false;
|
|
769
|
-
}
|
|
770
|
-
}
|